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.
- package/README.ja.md +77 -0
- package/README.md +4 -1
- package/dist/index.js +670 -88
- package/dist/templates/_shared/AGENTS.ja.md +104 -0
- package/dist/templates/_shared/AGENTS.md +104 -0
- package/dist/templates/_shared/README.ja.md +175 -0
- package/dist/templates/_shared/README.md +175 -0
- package/dist/templates/_shared/RUNBOOK.ja.md +120 -0
- package/dist/templates/_shared/RUNBOOK.md +24 -51
- package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +6 -0
- package/dist/templates/_shared/amplify/backend.custom.ts +41 -0
- package/dist/templates/_shared/amplify/backend.ts +12 -2
- package/dist/templates/_shared/amplify/data/get-published-post.js +9 -12
- package/dist/templates/_shared/amplify/data/list-posts-by-tag.js +6 -9
- package/dist/templates/_shared/amplify/data/list-published-posts.js +10 -11
- package/dist/templates/_shared/amplify/data/resource.custom.ts +32 -0
- package/dist/templates/_shared/amplify/data/resource.ts +26 -13
- package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +4 -0
- package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
- package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +11 -0
- package/dist/templates/_shared/amplify/functions/user-admin/handler.ts +4 -0
- package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +12 -0
- package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
- package/dist/templates/_shared/app/(admin)/admin/users/page.tsx +5 -0
- package/dist/templates/_shared/app/globals.css +55 -39
- package/dist/templates/_shared/app/layout.tsx +24 -11
- package/dist/templates/_shared/app/site/[siteId]/r/[slug]/[[...path]]/route.ts +17 -0
- package/dist/templates/_shared/cms.config.ts +1 -23
- package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +2 -3
- package/dist/templates/_shared/lib/admin.ts +9 -7
- package/dist/templates/_shared/lib/posts-public.ts +1 -1
- package/dist/templates/_shared/package.json +11 -9
- package/dist/templates/_shared/proxy.ts +3 -3
- package/dist/templates/blog/README.ja.md +22 -0
- package/dist/templates/blog/README.md +17 -47
- package/dist/templates/blog/manifest.ts +1 -1
- package/dist/templates/blog/pages/feed.ts +3 -4
- package/dist/templates/blog/pages/home.tsx +5 -8
- package/dist/templates/blog/pages/post.tsx +7 -7
- package/dist/templates/blog/pages/sitemap.ts +3 -4
- package/dist/templates/blog/pages/tag.tsx +4 -4
- package/dist/templates/blog/tokens.css +26 -40
- package/dist/templates/corporate/README.ja.md +18 -0
- package/dist/templates/corporate/README.md +12 -14
- package/dist/templates/corporate/pages/feed.ts +2 -3
- package/dist/templates/corporate/pages/home.tsx +5 -8
- package/dist/templates/corporate/pages/post.tsx +7 -7
- package/dist/templates/corporate/pages/sitemap.ts +2 -3
- package/dist/templates/corporate/pages/tag.tsx +4 -4
- package/dist/templates/corporate/tokens.css +17 -39
- package/dist/templates/dads/README.ja.md +31 -0
- package/dist/templates/dads/README.md +13 -17
- package/dist/templates/dads/pages/feed.ts +2 -3
- package/dist/templates/dads/pages/home.tsx +5 -8
- package/dist/templates/dads/pages/post.tsx +7 -7
- package/dist/templates/dads/pages/sitemap.ts +2 -3
- package/dist/templates/dads/pages/tag.tsx +4 -4
- package/dist/templates/dads/tokens.css +22 -42
- package/dist/templates/docs/README.ja.md +24 -0
- package/dist/templates/docs/README.md +10 -13
- package/dist/templates/docs/pages/feed.ts +2 -3
- package/dist/templates/docs/pages/home.tsx +5 -6
- package/dist/templates/docs/pages/post.tsx +8 -8
- package/dist/templates/docs/pages/sitemap.ts +2 -3
- package/dist/templates/docs/pages/tag.tsx +5 -5
- package/dist/templates/docs/tokens.css +17 -39
- package/dist/templates/landing/README.ja.md +20 -0
- package/dist/templates/landing/README.md +14 -19
- package/dist/templates/landing/pages/feed.ts +3 -4
- package/dist/templates/landing/pages/home.tsx +5 -8
- package/dist/templates/landing/pages/post.tsx +7 -7
- package/dist/templates/landing/pages/sitemap.ts +2 -3
- package/dist/templates/landing/pages/tag.tsx +4 -4
- package/dist/templates/landing/tokens.css +17 -39
- package/dist/templates/minimal/README.ja.md +14 -0
- package/dist/templates/minimal/README.md +9 -47
- package/dist/templates/minimal/pages/feed.ts +3 -4
- package/dist/templates/minimal/pages/home.tsx +3 -4
- package/dist/templates/minimal/pages/post.tsx +6 -6
- package/dist/templates/minimal/pages/sitemap.ts +3 -4
- package/dist/templates/minimal/pages/tag.tsx +3 -3
- package/dist/templates/minimal/tokens.css +17 -39
- package/package.json +1 -1
- package/dist/templates/_shared/app/(admin)/admin/sites/page.tsx +0 -5
- package/dist/templates/_shared/app/site/[siteId]/raw/[slug]/route.ts +0 -5
- package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
- 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
|
|
5
|
-
import { existsSync as
|
|
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
|
|
231
|
+
const entries = await readdir2(dirPath, { withFileTypes: true });
|
|
135
232
|
await Promise.all(
|
|
136
233
|
entries.map(async (entry) => {
|
|
137
|
-
const fullPath =
|
|
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@
|
|
332
|
-
npx create-ampless@
|
|
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
|
|
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
|
|
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 (!
|
|
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) =>
|
|
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 (
|
|
1030
|
-
await writeFile2(target,
|
|
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/
|
|
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 ?? [
|
|
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
|
-
|
|
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
|
-
|
|
1643
|
-
|
|
1644
|
-
"Run `npx create-ampless@
|
|
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
|
-
|
|
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
|
-
`${
|
|
2238
|
+
`${pc5.green("\u2714")} Project deployed`,
|
|
1664
2239
|
``,
|
|
1665
|
-
` GitHub: ${
|
|
1666
|
-
` Amplify app: ${
|
|
1667
|
-
` Amplify URL: ${
|
|
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: ${
|
|
2245
|
+
lines.push(` Custom domain: ${pc5.cyan(result.domainUrl)}`);
|
|
1671
2246
|
}
|
|
1672
2247
|
if (result.domainVerification && result.domainVerification.length > 0) {
|
|
1673
|
-
lines.push("", ` ${
|
|
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 ${
|
|
2256
|
+
` Watch it at ${pc5.cyan(`https://console.aws.amazon.com/amplify/home#/${result.amplifyAppId}`)}`
|
|
1682
2257
|
);
|
|
1683
|
-
|
|
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
|
-
|
|
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 (
|
|
1707
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2314
|
+
log5.error(err instanceof Error ? err.message : String(err));
|
|
1732
2315
|
process.exit(1);
|
|
1733
2316
|
}
|
|
1734
2317
|
return;
|
|
1735
2318
|
}
|
|
1736
|
-
|
|
1737
|
-
`${
|
|
2319
|
+
outro3(
|
|
2320
|
+
`${pc5.green("\u2714")} Project created at ${pc5.bold(opts.projectName)}
|
|
1738
2321
|
|
|
1739
2322
|
Next steps:
|
|
1740
|
-
${
|
|
1741
|
-
${
|
|
1742
|
-
${
|
|
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) => {
|