create-ampless 0.2.0-alpha.8 → 1.0.0-alpha.39

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 (87) hide show
  1. package/README.ja.md +77 -0
  2. package/README.md +4 -1
  3. package/dist/index.js +670 -88
  4. package/dist/templates/_shared/AGENTS.ja.md +104 -0
  5. package/dist/templates/_shared/AGENTS.md +104 -0
  6. package/dist/templates/_shared/README.ja.md +175 -0
  7. package/dist/templates/_shared/README.md +175 -0
  8. package/dist/templates/_shared/RUNBOOK.ja.md +120 -0
  9. package/dist/templates/_shared/RUNBOOK.md +24 -51
  10. package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +6 -0
  11. package/dist/templates/_shared/amplify/backend.custom.ts +41 -0
  12. package/dist/templates/_shared/amplify/backend.ts +12 -2
  13. package/dist/templates/_shared/amplify/data/get-published-post.js +9 -12
  14. package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +6 -9
  15. package/dist/templates/_shared/amplify/data/list-published-posts.js +10 -11
  16. package/dist/templates/_shared/amplify/data/resource.custom.ts +32 -0
  17. package/dist/templates/_shared/amplify/data/resource.ts +26 -13
  18. package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +4 -0
  19. package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
  20. package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +11 -0
  21. package/dist/templates/_shared/amplify/functions/user-admin/handler.ts +4 -0
  22. package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +12 -0
  23. package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
  24. package/dist/templates/_shared/app/(admin)/admin/users/page.tsx +5 -0
  25. package/dist/templates/_shared/app/globals.css +55 -39
  26. package/dist/templates/_shared/app/layout.tsx +24 -11
  27. package/dist/templates/_shared/app/site/[siteId]/r/[slug]/[[...path]]/route.ts +17 -0
  28. package/dist/templates/_shared/cms.config.ts +1 -23
  29. package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +2 -3
  30. package/dist/templates/_shared/lib/admin.ts +9 -7
  31. package/dist/templates/_shared/lib/posts-public.ts +1 -1
  32. package/dist/templates/_shared/package.json +11 -9
  33. package/dist/templates/_shared/proxy.ts +3 -3
  34. package/dist/templates/blog/README.ja.md +22 -0
  35. package/dist/templates/blog/README.md +17 -47
  36. package/dist/templates/blog/manifest.ts +1 -1
  37. package/dist/templates/blog/pages/feed.ts +3 -4
  38. package/dist/templates/blog/pages/home.tsx +5 -8
  39. package/dist/templates/blog/pages/post.tsx +7 -7
  40. package/dist/templates/blog/pages/sitemap.ts +3 -4
  41. package/dist/templates/blog/pages/tag.tsx +4 -4
  42. package/dist/templates/blog/tokens.css +26 -40
  43. package/dist/templates/corporate/README.ja.md +18 -0
  44. package/dist/templates/corporate/README.md +12 -14
  45. package/dist/templates/corporate/pages/feed.ts +2 -3
  46. package/dist/templates/corporate/pages/home.tsx +5 -8
  47. package/dist/templates/corporate/pages/post.tsx +7 -7
  48. package/dist/templates/corporate/pages/sitemap.ts +2 -3
  49. package/dist/templates/corporate/pages/tag.tsx +4 -4
  50. package/dist/templates/corporate/tokens.css +17 -39
  51. package/dist/templates/dads/README.ja.md +31 -0
  52. package/dist/templates/dads/README.md +13 -17
  53. package/dist/templates/dads/pages/feed.ts +2 -3
  54. package/dist/templates/dads/pages/home.tsx +5 -8
  55. package/dist/templates/dads/pages/post.tsx +7 -7
  56. package/dist/templates/dads/pages/sitemap.ts +2 -3
  57. package/dist/templates/dads/pages/tag.tsx +4 -4
  58. package/dist/templates/dads/tokens.css +22 -42
  59. package/dist/templates/docs/README.ja.md +24 -0
  60. package/dist/templates/docs/README.md +10 -13
  61. package/dist/templates/docs/pages/feed.ts +2 -3
  62. package/dist/templates/docs/pages/home.tsx +5 -6
  63. package/dist/templates/docs/pages/post.tsx +8 -8
  64. package/dist/templates/docs/pages/sitemap.ts +2 -3
  65. package/dist/templates/docs/pages/tag.tsx +5 -5
  66. package/dist/templates/docs/tokens.css +17 -39
  67. package/dist/templates/landing/README.ja.md +20 -0
  68. package/dist/templates/landing/README.md +14 -19
  69. package/dist/templates/landing/pages/feed.ts +3 -4
  70. package/dist/templates/landing/pages/home.tsx +5 -8
  71. package/dist/templates/landing/pages/post.tsx +7 -7
  72. package/dist/templates/landing/pages/sitemap.ts +2 -3
  73. package/dist/templates/landing/pages/tag.tsx +4 -4
  74. package/dist/templates/landing/tokens.css +17 -39
  75. package/dist/templates/minimal/README.ja.md +14 -0
  76. package/dist/templates/minimal/README.md +9 -47
  77. package/dist/templates/minimal/pages/feed.ts +3 -4
  78. package/dist/templates/minimal/pages/home.tsx +3 -4
  79. package/dist/templates/minimal/pages/post.tsx +6 -6
  80. package/dist/templates/minimal/pages/sitemap.ts +3 -4
  81. package/dist/templates/minimal/pages/tag.tsx +3 -3
  82. package/dist/templates/minimal/tokens.css +17 -39
  83. package/package.json +1 -1
  84. package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +0 -5
  85. package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +0 -5
  86. package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
  87. package/dist/templates/_shared/lib/admin-site.ts +0 -12
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { spinner as spinner2, outro, log as log3 } from "@clack/prompts";
5
- import { existsSync as existsSync4 } from "fs";
4
+ import { spinner as spinner2, outro as outro3, log as log5 } from "@clack/prompts";
5
+ import { existsSync as existsSync7 } from "fs";
6
6
  import { basename as basename3, resolve as resolve5 } from "path";
7
7
 
8
8
  // src/prompts.ts
@@ -28,7 +28,10 @@ async function runPrompts(argProjectName) {
28
28
  // Multiple themes can ship side-by-side. The first selected is the
29
29
  // default active theme; admins can switch per-site at runtime. Add
30
30
  // / remove themes later by editing themes-registry.ts and
31
- // themes/<name>/.
31
+ // themes/<name>/. Initial values pick every shipped theme so the
32
+ // shared `themes-registry.ts` placeholder (which imports all of
33
+ // them) compiles out of the box; deselect any the project won't
34
+ // need before confirming.
32
35
  themes: () => p.multiselect({
33
36
  message: "Themes to install (space to toggle)",
34
37
  options: [
@@ -39,7 +42,7 @@ async function runPrompts(argProjectName) {
39
42
  { value: "docs", label: "Docs \u2014 sidebar-led docs (tag-driven sections)" },
40
43
  { value: "dads", label: "DADS \u2014 Digital Agency Design System (Japanese government style)" }
41
44
  ],
42
- initialValues: ["blog"],
45
+ initialValues: ["blog", "minimal", "landing", "corporate", "docs", "dads"],
43
46
  required: true
44
47
  }),
45
48
  plugins: () => p.multiselect({
@@ -83,8 +86,8 @@ async function runPrompts(argProjectName) {
83
86
  }
84
87
 
85
88
  // src/scaffold.ts
86
- import { cp, readFile, writeFile, readdir, mkdir, rm } from "fs/promises";
87
- import { join, extname, resolve as resolve2 } from "path";
89
+ import { cp, readFile, writeFile, readdir as readdir2, mkdir, rm } from "fs/promises";
90
+ import { join as join2, extname, resolve as resolve2 } from "path";
88
91
 
89
92
  // src/templates.ts
90
93
  import { existsSync } from "fs";
@@ -104,6 +107,100 @@ function templatePath(theme) {
104
107
  return resolve(templatesDir, theme);
105
108
  }
106
109
 
110
+ // src/gitignore.ts
111
+ var DEFAULT_GITIGNORE = `# Dependencies
112
+ node_modules/
113
+
114
+ # Next.js
115
+ .next/
116
+ next-env.d.ts
117
+
118
+ # Amplify
119
+ .amplify/
120
+ amplify_outputs.json
121
+
122
+ # TypeScript build artifacts
123
+ *.tsbuildinfo
124
+
125
+ # Env / OS noise
126
+ .env
127
+ .env.local
128
+ .env.*.local
129
+ .DS_Store
130
+
131
+ # Logs
132
+ npm-debug.log*
133
+ yarn-debug.log*
134
+ yarn-error.log*
135
+
136
+ # Editor
137
+ .vscode/
138
+ .idea/
139
+ `;
140
+
141
+ // src/themes-registry.ts
142
+ import { readdir } from "fs/promises";
143
+ import { existsSync as existsSync2 } from "fs";
144
+ import { join } from "path";
145
+ var CUSTOM_THEME_PREFIX = "my-";
146
+ function toIdentifier(themeName) {
147
+ if (!themeName.includes("-")) return themeName;
148
+ return themeName.replace(/-([a-z0-9])/g, (_, c) => c.toUpperCase());
149
+ }
150
+ function isCustomTheme(themeName) {
151
+ return themeName.startsWith(CUSTOM_THEME_PREFIX);
152
+ }
153
+ async function discoverInstalledThemes(destDir) {
154
+ const themesDir = join(destDir, "themes");
155
+ if (!existsSync2(themesDir)) return [];
156
+ const entries = await readdir(themesDir, { withFileTypes: true });
157
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
158
+ }
159
+ function buildRegistry(themes) {
160
+ const sorted = [...themes].sort((a, b) => {
161
+ const ca = isCustomTheme(a);
162
+ const cb = isCustomTheme(b);
163
+ if (ca !== cb) return ca ? 1 : -1;
164
+ return a.localeCompare(b);
165
+ });
166
+ const imports = sorted.map((t) => `import ${toIdentifier(t)} from '@/themes/${t}'`).join("\n");
167
+ const map = sorted.map((t) => {
168
+ const id = toIdentifier(t);
169
+ return id === t ? ` ${t},` : ` '${t}': ${id},`;
170
+ }).join("\n");
171
+ const fallback = sorted[0] ?? "blog";
172
+ return `// Generated by create-ampless. Lists every theme installed under
173
+ // \`themes/<name>/\`. Auto-managed: \`create-ampless upgrade\` and
174
+ // \`copy-theme\` regenerate this file from the directories present
175
+ // under \`themes/\`. Edits made by hand will survive only until the
176
+ // next upgrade.
177
+ //
178
+ // \`theme.active\` (per-site, in KvStore) picks which theme renders for
179
+ // each request; everything listed here gets bundled so switching is
180
+ // instant. Custom themes use the \`${CUSTOM_THEME_PREFIX}\` prefix and
181
+ // are never touched by upgrade \u2014 clone an ampless default theme into
182
+ // a \`${CUSTOM_THEME_PREFIX}<name>\` directory via \`copy-theme\` before
183
+ // modifying it.
184
+
185
+ ${imports}
186
+
187
+ export const themes = {
188
+ ${map}
189
+ } as const
190
+
191
+ export type ThemeName = keyof typeof themes
192
+
193
+ export const themeList = Object.values(themes)
194
+
195
+ /**
196
+ * Default theme used when a site has no \`theme.active\` override.
197
+ * Falls back to the first registered theme so an empty registry would
198
+ * still surface a clear runtime error rather than silent breakage.
199
+ */
200
+ export const DEFAULT_THEME: ThemeName = (themeList[0]?.name as ThemeName) ?? '${fallback}'
201
+ `;
202
+ }
203
+
107
204
  // src/scaffold.ts
108
205
  var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
109
206
  ".json",
@@ -131,10 +228,10 @@ async function substituteFile(filePath, vars) {
131
228
  if (replaced !== content) await writeFile(filePath, replaced, "utf-8");
132
229
  }
133
230
  async function substituteDir(dirPath, vars) {
134
- const entries = await readdir(dirPath, { withFileTypes: true });
231
+ const entries = await readdir2(dirPath, { withFileTypes: true });
135
232
  await Promise.all(
136
233
  entries.map(async (entry) => {
137
- const fullPath = join(dirPath, entry.name);
234
+ const fullPath = join2(dirPath, entry.name);
138
235
  if (entry.isDirectory()) {
139
236
  await substituteDir(fullPath, vars);
140
237
  } else {
@@ -143,30 +240,6 @@ async function substituteDir(dirPath, vars) {
143
240
  })
144
241
  );
145
242
  }
146
- function buildRegistry(themes) {
147
- const imports = themes.map((t) => `import ${t} from '@/themes/${t}'`).join("\n");
148
- const map = themes.map((t) => ` ${t},`).join("\n");
149
- return `// Generated by create-ampless. Lists every theme installed under
150
- // \`themes/<name>/\`. Adding a theme = drop a directory under themes/,
151
- // add an import + map entry below, and redeploy.
152
- //
153
- // \`theme.active\` (per-site, in KvStore) picks which theme renders for
154
- // each request; everything listed here gets bundled so switching is
155
- // instant.
156
-
157
- ${imports}
158
-
159
- export const themes = {
160
- ${map}
161
- } as const
162
-
163
- export type ThemeName = keyof typeof themes
164
-
165
- export const themeList = Object.values(themes)
166
-
167
- export const DEFAULT_THEME: ThemeName = (themeList[0]?.name as ThemeName) ?? '${themes[0]}'
168
- `;
169
- }
170
243
  async function scaffold(sharedDir, themesRoot, destDir, opts) {
171
244
  await cp(sharedDir, destDir, { recursive: true });
172
245
  const destThemesDir = resolve2(destDir, "themes");
@@ -180,6 +253,8 @@ async function scaffold(sharedDir, themesRoot, destDir, opts) {
180
253
  void themesRoot;
181
254
  const registryPath = resolve2(destDir, "themes-registry.ts");
182
255
  await writeFile(registryPath, buildRegistry(opts.themes), "utf-8");
256
+ const gitignorePath = resolve2(destDir, ".gitignore");
257
+ await writeFile(gitignorePath, DEFAULT_GITIGNORE, "utf-8");
183
258
  const vars = {
184
259
  projectName: opts.projectName,
185
260
  siteName: opts.siteName,
@@ -209,6 +284,9 @@ var STRING_FLAGS = /* @__PURE__ */ new Set([
209
284
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
210
285
  "--deploy",
211
286
  "--mount",
287
+ "--upgrade",
288
+ "--dry-run",
289
+ "--no-install",
212
290
  "--github-private",
213
291
  "--create-iam-role",
214
292
  "--skip-confirm",
@@ -219,6 +297,10 @@ function parseDeployArgs(argv) {
219
297
  const out = {
220
298
  deploy: false,
221
299
  mount: false,
300
+ upgrade: false,
301
+ copyTheme: false,
302
+ dryRun: false,
303
+ noInstall: false,
222
304
  githubPrivate: false,
223
305
  createIamRole: false,
224
306
  skipConfirm: false,
@@ -242,6 +324,15 @@ function parseDeployArgs(argv) {
242
324
  case "--mount":
243
325
  out.mount = true;
244
326
  break;
327
+ case "--upgrade":
328
+ out.upgrade = true;
329
+ break;
330
+ case "--dry-run":
331
+ out.dryRun = true;
332
+ break;
333
+ case "--no-install":
334
+ out.noInstall = true;
335
+ break;
245
336
  case "--github-private":
246
337
  out.githubPrivate = true;
247
338
  break;
@@ -317,6 +408,22 @@ function parseDeployArgs(argv) {
317
408
  out.unknown.push(raw);
318
409
  continue;
319
410
  }
411
+ if (raw === "upgrade" && out.projectName === void 0) {
412
+ out.upgrade = true;
413
+ continue;
414
+ }
415
+ if (raw === "copy-theme" && out.projectName === void 0 && !out.copyTheme) {
416
+ out.copyTheme = true;
417
+ continue;
418
+ }
419
+ if (out.copyTheme && out.copyThemeSource === void 0) {
420
+ out.copyThemeSource = raw;
421
+ continue;
422
+ }
423
+ if (out.copyTheme && out.copyThemeTarget === void 0) {
424
+ out.copyThemeTarget = raw;
425
+ continue;
426
+ }
320
427
  if (out.projectName === void 0) {
321
428
  out.projectName = raw;
322
429
  } else {
@@ -328,8 +435,9 @@ function parseDeployArgs(argv) {
328
435
  var HELP_TEXT = `create-ampless \u2014 scaffold an ampless project
329
436
 
330
437
  Usage:
331
- npx create-ampless@alpha <project-name> [options]
332
- npx create-ampless@alpha --mount [options] # in an existing project dir
438
+ npx create-ampless@latest <project-name> [options]
439
+ npx create-ampless@latest --mount [options] # in an existing project dir
440
+ npx create-ampless@latest upgrade [options] # in an existing project dir
333
441
 
334
442
  Options:
335
443
  --site-name <name> Site display name (default: "My Blog")
@@ -365,6 +473,21 @@ Options:
365
473
  --skip-confirm Skip all interactive prompts and use defaults /
366
474
  flag values (for CI / automation)
367
475
  -h, --help Show this message
476
+
477
+ upgrade Sync ampless package files / deps to latest alpha.
478
+ Run inside an existing ampless project.
479
+
480
+ --dry-run Show what would change without writing any files
481
+ --no-install Skip running pnpm/npm install after updating
482
+ package.json
483
+
484
+ copy-theme <source> <target>
485
+ Copy a theme so the original stays managed by ampless and
486
+ customisations live in the copy. Target must start with "my-"
487
+ (the convention that flags it as user-owned, so upgrade leaves
488
+ it alone). Run inside an existing ampless project.
489
+
490
+ Example: npx create-ampless@latest copy-theme blog my-blog
368
491
  `;
369
492
 
370
493
  // src/deploy-prompts.ts
@@ -373,8 +496,8 @@ import { execa as execa3 } from "execa";
373
496
 
374
497
  // src/deploy.ts
375
498
  import { execa as execa2 } from "execa";
376
- import { existsSync as existsSync3 } from "fs";
377
- import { writeFile as writeFile2 } from "fs/promises";
499
+ import { existsSync as existsSync4 } from "fs";
500
+ import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
378
501
  import { basename as basename2, resolve as resolve4 } from "path";
379
502
  import { spinner, log } from "@clack/prompts";
380
503
  import pc2 from "picocolors";
@@ -852,42 +975,21 @@ function printPreflightReport(problems) {
852
975
  }
853
976
 
854
977
  // src/mount.ts
855
- import { existsSync as existsSync2 } from "fs";
978
+ import { existsSync as existsSync3 } from "fs";
856
979
  import { resolve as resolve3 } from "path";
857
980
  function validateMountableProject(destDir) {
858
981
  const required = ["package.json", "cms.config.ts"];
859
982
  for (const f of required) {
860
- if (!existsSync2(resolve3(destDir, f))) {
983
+ if (!existsSync3(resolve3(destDir, f))) {
861
984
  return `Not an ampless project (missing ${f} in ${destDir})`;
862
985
  }
863
986
  }
864
987
  const amplifyFiles = ["amplify/backend.ts", "amplify/data/resource.ts"];
865
- if (!amplifyFiles.some((f) => existsSync2(resolve3(destDir, f)))) {
988
+ if (!amplifyFiles.some((f) => existsSync3(resolve3(destDir, f)))) {
866
989
  return `Not an ampless project (missing amplify/ in ${destDir})`;
867
990
  }
868
991
  return null;
869
992
  }
870
- var MOUNT_DEFAULT_GITIGNORE = `# Dependencies
871
- node_modules/
872
-
873
- # Next.js
874
- .next/
875
- .amplify/
876
-
877
- # Amplify outputs (regenerated on every deploy / sandbox)
878
- amplify_outputs.json
879
-
880
- # Env / OS noise
881
- .env
882
- .env.local
883
- .env.*.local
884
- .DS_Store
885
-
886
- # Logs
887
- npm-debug.log*
888
- yarn-debug.log*
889
- yarn-error.log*
890
- `;
891
993
  function originPointsAt(origin, owner, name) {
892
994
  const candidates = [
893
995
  `https://github.com/${owner}/${name}`,
@@ -986,6 +1088,23 @@ function splitDomain(domain, subdomain) {
986
1088
  const prefix = domain.slice(0, domain.length - registrable.length - 1);
987
1089
  return { registrable, subdomain: prefix };
988
1090
  }
1091
+ async function rewriteCmsConfigForDomain(projectDir, fullDomain) {
1092
+ const path = resolve4(projectDir, "cms.config.ts");
1093
+ if (!existsSync4(path)) {
1094
+ return { urlRewritten: false };
1095
+ }
1096
+ let content = await readFile2(path, "utf-8");
1097
+ let urlRewritten = false;
1098
+ const urlRe = /url:\s*['"]http:\/\/localhost:3000['"]/;
1099
+ if (urlRe.test(content)) {
1100
+ content = content.replace(urlRe, `url: 'https://${fullDomain}'`);
1101
+ urlRewritten = true;
1102
+ }
1103
+ if (urlRewritten) {
1104
+ await writeFile2(path, content, "utf-8");
1105
+ }
1106
+ return { urlRewritten };
1107
+ }
989
1108
  async function run(cmd, args, opts = {}) {
990
1109
  const { step, ...execaOpts } = opts;
991
1110
  try {
@@ -1026,8 +1145,8 @@ async function currentBranch(dir) {
1026
1145
  }
1027
1146
  async function ensureGitignore(dir) {
1028
1147
  const target = resolve4(dir, ".gitignore");
1029
- if (existsSync3(target)) return;
1030
- await writeFile2(target, MOUNT_DEFAULT_GITIGNORE, "utf-8");
1148
+ if (existsSync4(target)) return;
1149
+ await writeFile2(target, DEFAULT_GITIGNORE, "utf-8");
1031
1150
  }
1032
1151
  async function gitInitOrReuse(dir, mount) {
1033
1152
  const repo = await isGitRepo(dir);
@@ -1361,6 +1480,14 @@ async function runDeploy(opts) {
1361
1480
  }
1362
1481
  }
1363
1482
  await writeFile2(resolve4(opts.projectDir, "amplify.yml"), AMPLIFY_BUILD_SPEC, "utf-8");
1483
+ if (opts.domain) {
1484
+ const { registrable, subdomain } = splitDomain(opts.domain, opts.subdomain);
1485
+ const fullDomain = subdomain ? `${subdomain}.${registrable}` : registrable;
1486
+ const mutations = await rewriteCmsConfigForDomain(opts.projectDir, fullDomain);
1487
+ if (mutations.urlRewritten) {
1488
+ log.info(`cms.config.ts updated: site.url \u2192 https://${fullDomain}`);
1489
+ }
1490
+ }
1364
1491
  const created = {};
1365
1492
  const fail = (step, cause) => {
1366
1493
  const msg = cause instanceof Error ? cause.message : String(cause);
@@ -1605,15 +1732,463 @@ async function gatherDeployOptions(args, projectDir, projectName) {
1605
1732
  };
1606
1733
  }
1607
1734
 
1608
- // src/index.ts
1735
+ // src/upgrade.ts
1736
+ import { cp as cp2, readFile as readFile3, writeFile as writeFile3, readdir as readdir3, mkdir as mkdir2, rm as rm2 } from "fs/promises";
1737
+ import { existsSync as existsSync5 } from "fs";
1738
+ import { join as join3, relative, extname as extname2, dirname } from "path";
1739
+ import { log as log3, outro } from "@clack/prompts";
1609
1740
  import pc3 from "picocolors";
1741
+ import { execa as execa4 } from "execa";
1742
+ var AMPLESS_MANAGED_APP_PATHS = [
1743
+ "app/(admin)/admin",
1744
+ "app/api/admin",
1745
+ "app/api/media",
1746
+ "app/api/mcp",
1747
+ "app/login",
1748
+ "app/site"
1749
+ ];
1750
+ var AMPLESS_PACKAGES = /* @__PURE__ */ new Set([
1751
+ "ampless",
1752
+ "@ampless/admin",
1753
+ "@ampless/backend",
1754
+ "@ampless/runtime",
1755
+ "@ampless/plugin-seo",
1756
+ "@ampless/plugin-rss",
1757
+ "@ampless/plugin-webhook",
1758
+ "@ampless/plugin-og-image"
1759
+ ]);
1760
+ var AMPLESS_MANAGED_SCRIPTS = /* @__PURE__ */ new Set([
1761
+ "sandbox",
1762
+ "sandbox:dev",
1763
+ "update-ampless",
1764
+ "copy-theme"
1765
+ ]);
1766
+ var PROTECTED_PATTERNS = [
1767
+ /^cms\.config\.ts$/,
1768
+ // `themes/` and `themes-registry.ts` are handled separately — see
1769
+ // syncThemes() below. The themes directory is owned per-entry by
1770
+ // either ampless (default themes, resynced on upgrade) or the user
1771
+ // (`my-*` themes, preserved). The registry is regenerated to match
1772
+ // whatever is on disk afterward.
1773
+ /^\.env/,
1774
+ /^node_modules(\/|$)/,
1775
+ /^\.next(\/|$)/,
1776
+ /^\.turbo(\/|$)/,
1777
+ /^\.amplify(\/|$)/,
1778
+ /^amplify_outputs\.json$/,
1779
+ /^next-env\.d\.ts$/,
1780
+ /^tsconfig\.tsbuildinfo$/,
1781
+ /^pnpm-lock\.yaml$/,
1782
+ /^package-lock\.json$/,
1783
+ // Anything under themes/ is handled by syncThemes; the file-walk
1784
+ // classifier must not double-process it.
1785
+ /^themes(\/|$)/,
1786
+ /^themes-registry\.ts$/
1787
+ ];
1788
+ var SEED_IF_MISSING_PATTERN = /\.custom\.ts$/;
1789
+ var TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
1790
+ ".json",
1791
+ ".md",
1792
+ ".ts",
1793
+ ".tsx",
1794
+ ".js",
1795
+ ".jsx",
1796
+ ".mjs",
1797
+ ".cjs",
1798
+ ".html",
1799
+ ".css",
1800
+ ".env",
1801
+ ".txt",
1802
+ ".yaml",
1803
+ ".yml",
1804
+ ".toml",
1805
+ ".gitignore"
1806
+ ]);
1807
+ function isProtected(relPath) {
1808
+ return PROTECTED_PATTERNS.some((re) => re.test(relPath));
1809
+ }
1810
+ async function listShippedThemes(templatesRoot) {
1811
+ if (!existsSync5(templatesRoot)) return [];
1812
+ const entries = await readdir3(templatesRoot, { withFileTypes: true });
1813
+ return entries.filter((e) => e.isDirectory() && e.name !== "_shared").map((e) => e.name).sort();
1814
+ }
1815
+ var USER_OWNED_THEME_FILES = /* @__PURE__ */ new Set(["README.md", ".gitignore"]);
1816
+ function isUserOwnedThemeFile(path) {
1817
+ const name = path.split(/[/\\]/).pop() ?? "";
1818
+ return USER_OWNED_THEME_FILES.has(name);
1819
+ }
1820
+ async function captureUserOwnedFiles(dir) {
1821
+ const out = /* @__PURE__ */ new Map();
1822
+ if (!existsSync5(dir)) return out;
1823
+ for (const name of USER_OWNED_THEME_FILES) {
1824
+ const p3 = join3(dir, name);
1825
+ if (existsSync5(p3)) {
1826
+ out.set(name, await readFile3(p3));
1827
+ }
1828
+ }
1829
+ return out;
1830
+ }
1831
+ async function restorePreservedFiles(dir, files) {
1832
+ for (const [name, content] of files) {
1833
+ await mkdir2(dir, { recursive: true });
1834
+ await writeFile3(join3(dir, name), content);
1835
+ }
1836
+ }
1837
+ async function syncThemes(destDir, templatesRoot) {
1838
+ const shipped = await listShippedThemes(templatesRoot);
1839
+ const themesDir = join3(destDir, "themes");
1840
+ await mkdir2(themesDir, { recursive: true });
1841
+ for (const name of shipped) {
1842
+ const src = join3(templatesRoot, name);
1843
+ const dst = join3(themesDir, name);
1844
+ const preservedFiles = await captureUserOwnedFiles(dst);
1845
+ await rm2(dst, { recursive: true, force: true });
1846
+ await cp2(src, dst, {
1847
+ recursive: true,
1848
+ filter: (sourcePath) => !isUserOwnedThemeFile(sourcePath)
1849
+ });
1850
+ await restorePreservedFiles(dst, preservedFiles);
1851
+ }
1852
+ const installed = await discoverInstalledThemes(destDir);
1853
+ const shippedSet = new Set(shipped);
1854
+ const preserved = installed.filter((t) => !shippedSet.has(t));
1855
+ const all = [...shipped, ...preserved];
1856
+ await writeFile3(join3(destDir, "themes-registry.ts"), buildRegistry(all), "utf-8");
1857
+ return { synced: shipped, preserved };
1858
+ }
1859
+ async function walkDir(dir, base, out) {
1860
+ const entries = await readdir3(dir, { withFileTypes: true });
1861
+ for (const entry of entries) {
1862
+ const full = join3(dir, entry.name);
1863
+ const rel = relative(base, full);
1864
+ if (entry.isDirectory()) {
1865
+ await walkDir(full, base, out);
1866
+ } else {
1867
+ out.push(rel);
1868
+ }
1869
+ }
1870
+ }
1871
+ async function listFilesRecursive(root) {
1872
+ const out = [];
1873
+ async function walk(current, relPrefix) {
1874
+ const entries = await readdir3(current, { withFileTypes: true });
1875
+ for (const entry of entries) {
1876
+ const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
1877
+ if (entry.isDirectory()) {
1878
+ await walk(join3(current, entry.name), rel);
1879
+ } else if (entry.isFile()) {
1880
+ out.push(rel);
1881
+ }
1882
+ }
1883
+ }
1884
+ if (!existsSync5(root)) return out;
1885
+ await walk(root, "");
1886
+ return out;
1887
+ }
1888
+ async function pruneEmptyDirs(root) {
1889
+ if (!existsSync5(root)) return;
1890
+ const entries = await readdir3(root, { withFileTypes: true });
1891
+ for (const entry of entries) {
1892
+ if (entry.isDirectory()) {
1893
+ await pruneEmptyDirs(join3(root, entry.name));
1894
+ }
1895
+ }
1896
+ const remaining = await readdir3(root);
1897
+ if (remaining.length === 0) {
1898
+ await rm2(root, { recursive: true, force: true });
1899
+ }
1900
+ }
1901
+ async function findObsoleteAppFiles(destDir, sharedDir) {
1902
+ const obsolete = [];
1903
+ for (const managedPath of AMPLESS_MANAGED_APP_PATHS) {
1904
+ const userPath = join3(destDir, managedPath);
1905
+ if (!existsSync5(userPath)) continue;
1906
+ const templatePath2 = join3(sharedDir, managedPath);
1907
+ const templateFiles = new Set(await listFilesRecursive(templatePath2));
1908
+ const userFiles = await listFilesRecursive(userPath);
1909
+ for (const f of userFiles) {
1910
+ if (!templateFiles.has(f)) {
1911
+ obsolete.push(`${managedPath}/${f}`);
1912
+ }
1913
+ }
1914
+ }
1915
+ return obsolete;
1916
+ }
1917
+ async function removeObsoleteAppFiles(destDir, paths) {
1918
+ for (const rel of paths) {
1919
+ const abs = join3(destDir, rel);
1920
+ if (existsSync5(abs)) {
1921
+ await rm2(abs, { force: true });
1922
+ }
1923
+ }
1924
+ for (const managedPath of AMPLESS_MANAGED_APP_PATHS) {
1925
+ await pruneEmptyDirs(join3(destDir, managedPath));
1926
+ }
1927
+ }
1928
+ function substituteVars(content, vars) {
1929
+ return content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
1930
+ }
1931
+ async function copyWithSubstitution(src, dst, vars) {
1932
+ const ext = extname2(src) || (src.endsWith(".gitignore") ? ".gitignore" : "");
1933
+ await mkdir2(dirname(dst), { recursive: true });
1934
+ if (TEXT_EXTENSIONS2.has(ext)) {
1935
+ const content = await readFile3(src, "utf-8");
1936
+ await writeFile3(dst, substituteVars(content, vars), "utf-8");
1937
+ } else {
1938
+ const buf = await readFile3(src);
1939
+ await writeFile3(dst, buf);
1940
+ }
1941
+ }
1942
+ function detectIndent(jsonStr) {
1943
+ const m = jsonStr.match(/^{\n(\s+)/);
1944
+ if (!m) return 2;
1945
+ return m[1].length;
1946
+ }
1947
+ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
1948
+ const derivedRoot = opts.templatesRoot ?? join3(sharedDir, "..");
1949
+ const themeSyncEnabled = existsSync5(join3(derivedRoot, "_shared"));
1950
+ const templatesRoot = themeSyncEnabled ? derivedRoot : "";
1951
+ const problem = validateMountableProject(destDir);
1952
+ if (problem) {
1953
+ throw new Error(problem);
1954
+ }
1955
+ const projectPkgPath = join3(destDir, "package.json");
1956
+ const projectPkgRaw = await readFile3(projectPkgPath, "utf-8");
1957
+ const projectPkg = JSON.parse(projectPkgRaw);
1958
+ const projectName = typeof projectPkg.name === "string" && projectPkg.name ? projectPkg.name : "my-ampless-site";
1959
+ const vars = { projectName };
1960
+ const allRelPaths = [];
1961
+ await walkDir(sharedDir, sharedDir, allRelPaths);
1962
+ const classification = { replace: [], merge: [], seed: [], protected: [] };
1963
+ for (const rel of allRelPaths) {
1964
+ if (SEED_IF_MISSING_PATTERN.test(rel)) {
1965
+ classification.seed.push(rel);
1966
+ } else if (isProtected(rel)) {
1967
+ classification.protected.push(rel);
1968
+ } else if (rel === "package.json") {
1969
+ classification.merge.push(rel);
1970
+ } else {
1971
+ classification.replace.push(rel);
1972
+ }
1973
+ }
1974
+ const replaceNew = classification.replace.filter((r) => !existsSync5(join3(destDir, r)));
1975
+ const replaceUpdate = classification.replace.filter((r) => existsSync5(join3(destDir, r)));
1976
+ const seedNew = classification.seed.filter((r) => !existsSync5(join3(destDir, r)));
1977
+ const seedSkipped = classification.seed.filter((r) => existsSync5(join3(destDir, r)));
1978
+ const shippedThemes = themeSyncEnabled ? await listShippedThemes(templatesRoot) : [];
1979
+ const existingThemes = themeSyncEnabled ? await discoverInstalledThemes(destDir) : [];
1980
+ const preservedThemes = existingThemes.filter((t) => !shippedThemes.includes(t));
1981
+ const obsoleteFiles = await findObsoleteAppFiles(destDir, sharedDir);
1982
+ log3.info(
1983
+ `replace: ${pc3.green(`${replaceNew.length} added`)} / ${pc3.yellow(`${replaceUpdate.length} updated`)}`
1984
+ );
1985
+ log3.info(`merge: ${pc3.cyan("package.json: sync ampless deps and managed scripts with the template")}`);
1986
+ if (classification.seed.length > 0) {
1987
+ log3.info(
1988
+ `seed: ${pc3.green(`${seedNew.length} added`)} / ${pc3.dim(`${seedSkipped.length} kept (existing *.custom.ts left untouched)`)}`
1989
+ );
1990
+ }
1991
+ if (themeSyncEnabled) {
1992
+ log3.info(
1993
+ `themes: ${pc3.cyan(`${shippedThemes.length} default themes synced`)} / ${pc3.dim(`${preservedThemes.length} custom (my-*) themes preserved`)}`
1994
+ );
1995
+ }
1996
+ if (obsoleteFiles.length > 0) {
1997
+ log3.info(`cleanup: ${pc3.yellow(`${obsoleteFiles.length} removed`)} (files under ampless-managed app/ paths that no longer exist in the template)`);
1998
+ }
1999
+ log3.info(`protected: ${pc3.dim(`${classification.protected.length} template files left untouched`)}`);
2000
+ if (opts.dryRun) {
2001
+ return {
2002
+ added: replaceNew,
2003
+ updated: replaceUpdate,
2004
+ seeded: seedNew,
2005
+ protected: classification.protected,
2006
+ themesSynced: shippedThemes,
2007
+ themesPreserved: preservedThemes,
2008
+ packageJsonMerged: false,
2009
+ obsoleteRemoved: obsoleteFiles
2010
+ };
2011
+ }
2012
+ for (const rel of classification.replace) {
2013
+ const src = join3(sharedDir, rel);
2014
+ const dst = join3(destDir, rel);
2015
+ await copyWithSubstitution(src, dst, vars);
2016
+ }
2017
+ for (const rel of seedNew) {
2018
+ const src = join3(sharedDir, rel);
2019
+ const dst = join3(destDir, rel);
2020
+ await copyWithSubstitution(src, dst, vars);
2021
+ }
2022
+ const themeResult = themeSyncEnabled ? await syncThemes(destDir, templatesRoot) : { synced: [], preserved: [] };
2023
+ await removeObsoleteAppFiles(destDir, obsoleteFiles);
2024
+ const templatePkgRaw = await readFile3(join3(sharedDir, "package.json"), "utf-8");
2025
+ const templatePkg = JSON.parse(templatePkgRaw);
2026
+ const indent = detectIndent(projectPkgRaw);
2027
+ for (const section of ["dependencies", "devDependencies"]) {
2028
+ const templateDeps = templatePkg[section] ?? {};
2029
+ const projectDeps = projectPkg[section] ?? {};
2030
+ for (const [name, version] of Object.entries(templateDeps)) {
2031
+ if (!AMPLESS_PACKAGES.has(name)) continue;
2032
+ projectDeps[name] = version;
2033
+ }
2034
+ if (Object.keys(projectDeps).length > 0) {
2035
+ projectPkg[section] = projectDeps;
2036
+ }
2037
+ }
2038
+ const templateScripts = templatePkg["scripts"] ?? {};
2039
+ const projectScripts = projectPkg["scripts"] ?? {};
2040
+ for (const name of AMPLESS_MANAGED_SCRIPTS) {
2041
+ if (name in templateScripts) {
2042
+ projectScripts[name] = templateScripts[name];
2043
+ } else {
2044
+ delete projectScripts[name];
2045
+ }
2046
+ }
2047
+ if (Object.keys(projectScripts).length > 0) {
2048
+ projectPkg["scripts"] = projectScripts;
2049
+ }
2050
+ await writeFile3(projectPkgPath, JSON.stringify(projectPkg, null, indent) + "\n", "utf-8");
2051
+ if (!opts.noInstall) {
2052
+ const usePnpm = existsSync5(join3(destDir, "pnpm-lock.yaml"));
2053
+ const pm = usePnpm ? "pnpm" : "npm";
2054
+ await execa4(pm, ["install"], { cwd: destDir, stdio: "inherit" });
2055
+ }
2056
+ return {
2057
+ added: replaceNew,
2058
+ updated: replaceUpdate,
2059
+ seeded: seedNew,
2060
+ protected: classification.protected,
2061
+ themesSynced: themeResult.synced,
2062
+ themesPreserved: themeResult.preserved,
2063
+ packageJsonMerged: true,
2064
+ obsoleteRemoved: obsoleteFiles
2065
+ };
2066
+ }
2067
+ async function runUpgrade(args) {
2068
+ const destDir = process.cwd();
2069
+ try {
2070
+ await runUpgradeIn(destDir, sharedTemplateDir(), {
2071
+ dryRun: args.dryRun,
2072
+ noInstall: args.noInstall
2073
+ });
2074
+ } catch (err) {
2075
+ log3.error(err instanceof Error ? err.message : String(err));
2076
+ process.exit(1);
2077
+ }
2078
+ if (args.dryRun) {
2079
+ outro(`${pc3.dim("(dry-run) No files were changed.")}`);
2080
+ return;
2081
+ }
2082
+ outro(
2083
+ `${pc3.green("\u2714")} Upgrade complete
2084
+
2085
+ Next steps:
2086
+ ${pc3.cyan("git diff")} ${pc3.dim("# review changes")}
2087
+ ${pc3.cyan("git commit && git push")} ${pc3.dim("# deploy via Amplify Hosting")}`
2088
+ );
2089
+ }
2090
+
2091
+ // src/copy-theme.ts
2092
+ import { cp as cp3, readFile as readFile4, writeFile as writeFile4 } from "fs/promises";
2093
+ import { existsSync as existsSync6 } from "fs";
2094
+ import { join as join4 } from "path";
2095
+ import { log as log4, outro as outro2 } from "@clack/prompts";
2096
+ import pc4 from "picocolors";
2097
+ async function runCopyThemeIn(destDir, source, target) {
2098
+ const problem = validateMountableProject(destDir);
2099
+ if (problem) {
2100
+ throw new Error(problem);
2101
+ }
2102
+ if (!isCustomTheme(target)) {
2103
+ throw new Error(
2104
+ `Target theme name must start with "${CUSTOM_THEME_PREFIX}" (got "${target}"). This prefix marks the theme as user-owned so create-ampless upgrade leaves it alone.`
2105
+ );
2106
+ }
2107
+ if (source === target) {
2108
+ throw new Error(`Source and target are identical (${source}).`);
2109
+ }
2110
+ const themesDir = join4(destDir, "themes");
2111
+ const sourceDir = join4(themesDir, source);
2112
+ const targetDir = join4(themesDir, target);
2113
+ if (!existsSync6(sourceDir)) {
2114
+ throw new Error(`Source theme not found: themes/${source}/`);
2115
+ }
2116
+ if (existsSync6(targetDir)) {
2117
+ throw new Error(`Target theme already exists: themes/${target}/`);
2118
+ }
2119
+ await cp3(sourceDir, targetDir, { recursive: true });
2120
+ const filesRewritten = await rewriteThemeName(targetDir, source, target);
2121
+ const installed = await discoverInstalledThemes(destDir);
2122
+ await writeFile4(join4(destDir, "themes-registry.ts"), buildRegistry(installed), "utf-8");
2123
+ return { source, target, filesRewritten };
2124
+ }
2125
+ async function rewriteThemeName(targetDir, source, target) {
2126
+ const touched = [];
2127
+ await rewriteFile(
2128
+ join4(targetDir, "index.ts"),
2129
+ (s) => s.replace(new RegExp(`name:\\s*'${escapeRegex(source)}'`), `name: '${target}'`).replace(new RegExp(`name:\\s*"${escapeRegex(source)}"`), `name: "${target}"`),
2130
+ touched
2131
+ );
2132
+ await rewriteFile(
2133
+ join4(targetDir, "manifest.ts"),
2134
+ (s) => s.replace(new RegExp(`name:\\s*'${escapeRegex(source)}'`), `name: '${target}'`).replace(new RegExp(`name:\\s*"${escapeRegex(source)}"`), `name: "${target}"`),
2135
+ touched
2136
+ );
2137
+ await rewriteFile(
2138
+ join4(targetDir, "tokens.css"),
2139
+ (s) => s.replace(new RegExp(`\\[data-theme='${escapeRegex(source)}'\\]`, "g"), `[data-theme='${target}']`).replace(new RegExp(`\\[data-theme="${escapeRegex(source)}"\\]`, "g"), `[data-theme="${target}"]`),
2140
+ touched
2141
+ );
2142
+ return touched;
2143
+ }
2144
+ async function rewriteFile(path, transform, touched) {
2145
+ if (!existsSync6(path)) return;
2146
+ const before = await readFile4(path, "utf-8");
2147
+ const after = transform(before);
2148
+ if (after === before) return;
2149
+ await writeFile4(path, after, "utf-8");
2150
+ touched.push(path);
2151
+ }
2152
+ function escapeRegex(s) {
2153
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2154
+ }
2155
+ async function runCopyTheme(args) {
2156
+ const destDir = process.cwd();
2157
+ const source = args.copyThemeSource;
2158
+ const target = args.copyThemeTarget;
2159
+ if (!source || !target) {
2160
+ log4.error("Usage: npx create-ampless@latest copy-theme <source> <target>");
2161
+ log4.info("Example: npx create-ampless@latest copy-theme blog my-blog");
2162
+ process.exit(1);
2163
+ }
2164
+ try {
2165
+ const result = await runCopyThemeIn(destDir, source, target);
2166
+ outro2(
2167
+ `${pc4.green("\u2714")} Copied themes/${result.source}/ \u2192 themes/${result.target}/
2168
+
2169
+ Files rewritten:
2170
+ ` + result.filesRewritten.map((f) => ` ${pc4.dim(f)}`).join("\n") + `
2171
+
2172
+ themes-registry.ts updated. Next:
2173
+ ${pc4.cyan(`Open themes/${result.target}/ and start customising`)}
2174
+ ${pc4.cyan(`Activate via /admin/sites/default/theme`)}`
2175
+ );
2176
+ } catch (err) {
2177
+ log4.error(err instanceof Error ? err.message : String(err));
2178
+ process.exit(1);
2179
+ }
2180
+ }
2181
+
2182
+ // src/index.ts
2183
+ import pc5 from "picocolors";
2184
+ var DEFAULT_THEMES = VALID_THEMES;
1610
2185
  function buildNonInteractiveOpts(args) {
1611
2186
  const projectName = args.projectName ?? (() => {
1612
2187
  const b = basename3(process.cwd());
1613
2188
  return b && b !== "/" ? b : "my-ampless-site";
1614
2189
  })();
1615
2190
  const siteName = args.siteName ?? "My Blog";
1616
- const themes = args.themes ?? ["blog"];
2191
+ const themes = args.themes ?? [...DEFAULT_THEMES];
1617
2192
  const plugins = args.plugins ?? ["seo"];
1618
2193
  return {
1619
2194
  projectName,
@@ -1630,7 +2205,7 @@ function warnIgnoredScaffoldFlags(args) {
1630
2205
  if (args.plugins) ignored.push("--plugins");
1631
2206
  if (args.projectName) ignored.push("<project-name> positional");
1632
2207
  if (ignored.length > 0) {
1633
- log3.warn(`--mount mode ignores: ${ignored.join(", ")}`);
2208
+ log5.warn(`--mount mode ignores: ${ignored.join(", ")}`);
1634
2209
  }
1635
2210
  }
1636
2211
  async function runMount(args) {
@@ -1639,9 +2214,9 @@ async function runMount(args) {
1639
2214
  warnIgnoredScaffoldFlags(args);
1640
2215
  const problem = validateMountableProject(destDir);
1641
2216
  if (problem) {
1642
- log3.error(problem);
1643
- log3.info(
1644
- "Run `npx create-ampless@alpha <name>` first to scaffold, or `cd` into a scaffolded project before passing --mount."
2217
+ log5.error(problem);
2218
+ log5.info(
2219
+ "Run `npx create-ampless@latest <name>` first to scaffold, or `cd` into a scaffolded project before passing --mount."
1645
2220
  );
1646
2221
  process.exit(1);
1647
2222
  }
@@ -1654,23 +2229,23 @@ async function runMount(args) {
1654
2229
  if (err instanceof PreflightError) {
1655
2230
  process.exit(1);
1656
2231
  }
1657
- log3.error(err instanceof Error ? err.message : String(err));
2232
+ log5.error(err instanceof Error ? err.message : String(err));
1658
2233
  process.exit(1);
1659
2234
  }
1660
2235
  }
1661
2236
  function printDeployResult(result) {
1662
2237
  const lines = [
1663
- `${pc3.green("\u2714")} Project deployed`,
2238
+ `${pc5.green("\u2714")} Project deployed`,
1664
2239
  ``,
1665
- ` GitHub: ${pc3.cyan(result.githubRepoUrl)}`,
1666
- ` Amplify app: ${pc3.cyan(result.amplifyAppId)}`,
1667
- ` Amplify URL: ${pc3.cyan(result.amplifyAppUrl)}`
2240
+ ` GitHub: ${pc5.cyan(result.githubRepoUrl)}`,
2241
+ ` Amplify app: ${pc5.cyan(result.amplifyAppId)}`,
2242
+ ` Amplify URL: ${pc5.cyan(result.amplifyAppUrl)}`
1668
2243
  ];
1669
2244
  if (result.domainUrl) {
1670
- lines.push(` Custom domain: ${pc3.cyan(result.domainUrl)}`);
2245
+ lines.push(` Custom domain: ${pc5.cyan(result.domainUrl)}`);
1671
2246
  }
1672
2247
  if (result.domainVerification && result.domainVerification.length > 0) {
1673
- lines.push("", ` ${pc3.bold("Add these DNS records to verify the domain:")}`);
2248
+ lines.push("", ` ${pc5.bold("Add these DNS records to verify the domain:")}`);
1674
2249
  for (const v of result.domainVerification) {
1675
2250
  lines.push(` ${v.cname} CNAME ${v.target}`);
1676
2251
  }
@@ -1678,9 +2253,9 @@ function printDeployResult(result) {
1678
2253
  lines.push(
1679
2254
  "",
1680
2255
  ` First build is now running in Amplify Hosting.`,
1681
- ` Watch it at ${pc3.cyan(`https://console.aws.amazon.com/amplify/home#/${result.amplifyAppId}`)}`
2256
+ ` Watch it at ${pc5.cyan(`https://console.aws.amazon.com/amplify/home#/${result.amplifyAppId}`)}`
1682
2257
  );
1683
- outro(lines.join("\n"));
2258
+ outro3(lines.join("\n"));
1684
2259
  }
1685
2260
  async function main() {
1686
2261
  const args = parseDeployArgs(process.argv.slice(2));
@@ -1689,7 +2264,15 @@ async function main() {
1689
2264
  return;
1690
2265
  }
1691
2266
  for (const flag of args.unknown) {
1692
- log3.warn(`Unknown argument ignored: ${flag}`);
2267
+ log5.warn(`Unknown argument ignored: ${flag}`);
2268
+ }
2269
+ if (args.upgrade) {
2270
+ await runUpgrade(args);
2271
+ return;
2272
+ }
2273
+ if (args.copyTheme) {
2274
+ await runCopyTheme(args);
2275
+ return;
1693
2276
  }
1694
2277
  if (args.mount) {
1695
2278
  await runMount(args);
@@ -1703,8 +2286,8 @@ async function main() {
1703
2286
  }
1704
2287
  if (!opts) return;
1705
2288
  const destDir = resolve5(process.cwd(), opts.projectName);
1706
- if (existsSync4(destDir)) {
1707
- log3.error(`Directory already exists: ${destDir}`);
2289
+ if (existsSync7(destDir)) {
2290
+ log5.error(`Directory already exists: ${destDir}`);
1708
2291
  process.exit(1);
1709
2292
  }
1710
2293
  const sharedDir = sharedTemplateDir();
@@ -1715,7 +2298,7 @@ async function main() {
1715
2298
  s.stop("Done!");
1716
2299
  } catch (err) {
1717
2300
  s.stop("Failed.");
1718
- log3.error(String(err));
2301
+ log5.error(String(err));
1719
2302
  process.exit(1);
1720
2303
  }
1721
2304
  if (args.deploy) {
@@ -1728,19 +2311,18 @@ async function main() {
1728
2311
  if (err instanceof PreflightError) {
1729
2312
  process.exit(1);
1730
2313
  }
1731
- log3.error(err instanceof Error ? err.message : String(err));
2314
+ log5.error(err instanceof Error ? err.message : String(err));
1732
2315
  process.exit(1);
1733
2316
  }
1734
2317
  return;
1735
2318
  }
1736
- outro(
1737
- `${pc3.green("\u2714")} Project created at ${pc3.bold(opts.projectName)}
2319
+ outro3(
2320
+ `${pc5.green("\u2714")} Project created at ${pc5.bold(opts.projectName)}
1738
2321
 
1739
2322
  Next steps:
1740
- ${pc3.cyan("cd")} ${opts.projectName}
1741
- ${pc3.cyan("npm install")}
1742
- ${pc3.cyan("npx ampx sandbox")} ${pc3.dim("# start Amplify backend")}
1743
- ${pc3.cyan("npm run dev")} ${pc3.dim("# start Next.js")}`
2323
+ ${pc5.cyan("cd")} ${opts.projectName}
2324
+ ${pc5.cyan("npm install")}
2325
+ ${pc5.cyan("npm run sandbox")} ${pc5.dim("# deploy sandbox + start Next.js")}`
1744
2326
  );
1745
2327
  }
1746
2328
  main().catch((err) => {