create-ampless 0.2.0-alpha.9 → 1.0.0-alpha.100
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 +6 -3
- package/dist/index.js +743 -84
- package/dist/templates/_shared/AGENTS.ja.md +94 -0
- package/dist/templates/_shared/AGENTS.md +94 -0
- package/dist/templates/_shared/README.ja.md +239 -0
- package/dist/templates/_shared/README.md +239 -0
- package/dist/templates/_shared/RUNBOOK.ja.md +120 -0
- package/dist/templates/_shared/RUNBOOK.md +32 -58
- package/dist/templates/_shared/THEMES.ja.md +495 -0
- package/dist/templates/_shared/THEMES.md +523 -0
- package/dist/templates/_shared/amplify/auth/post-confirmation/resource.ts +7 -0
- package/dist/templates/_shared/amplify/backend.ts +10 -0
- package/dist/templates/_shared/amplify/data/get-media-by-src.js +45 -0
- 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 +1 -2
- package/dist/templates/_shared/amplify/data/resource.ts +28 -7
- package/dist/templates/_shared/amplify/events/dispatcher/resource.ts +1 -0
- package/dist/templates/_shared/amplify/events/processor-trusted/resource.ts +1 -0
- package/dist/templates/_shared/amplify/events/processor-untrusted/resource.ts +5 -0
- package/dist/templates/_shared/amplify/functions/api-key-renewer/resource.ts +1 -0
- package/dist/templates/_shared/amplify/functions/mcp-handler/handler.ts +1 -0
- package/dist/templates/_shared/amplify/functions/mcp-handler/resource.ts +12 -0
- package/dist/templates/_shared/amplify/functions/plugin-secret-handler/handler.ts +11 -0
- package/dist/templates/_shared/amplify/functions/plugin-secret-handler/resource.ts +12 -0
- package/dist/templates/_shared/amplify/functions/user-admin/resource.ts +7 -0
- package/dist/templates/_shared/amplify/secrets/.gitkeep +0 -0
- package/dist/templates/_shared/amplify/secrets/encryption-key.ts +9 -0
- package/dist/templates/_shared/app/(admin)/admin/mcp-tokens/page.tsx +5 -0
- package/dist/templates/_shared/app/(admin)/admin/plugins/page.tsx +5 -0
- package/dist/templates/_shared/app/globals.css +55 -39
- package/dist/templates/_shared/app/layout.tsx +51 -16
- package/dist/templates/_shared/app/providers.tsx +0 -2
- package/dist/templates/_shared/app/raw/[slug]/route.ts +10 -0
- package/dist/templates/_shared/app/static/[slug]/[[...path]]/route.ts +14 -0
- package/dist/templates/_shared/cms.config.ts +64 -23
- package/dist/templates/_shared/components/site-chrome/site-sidebar.tsx +3 -4
- package/dist/templates/_shared/components/tag-list.tsx +9 -2
- package/dist/templates/_shared/components.json +1 -1
- package/dist/templates/_shared/docs/plugin-author-guide.ja.md +934 -0
- package/dist/templates/_shared/docs/plugin-author-guide.md +1336 -0
- package/dist/templates/_shared/lib/admin.ts +12 -12
- package/dist/templates/_shared/lib/ampless.ts +2 -8
- package/dist/templates/_shared/lib/amplify.ts +0 -5
- package/dist/templates/_shared/package.json +51 -43
- package/dist/templates/_shared/plugins/README.ja.md +139 -0
- package/dist/templates/_shared/plugins/README.md +143 -0
- package/dist/templates/_shared/proxy.ts +23 -8
- 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 +4 -5
- package/dist/templates/blog/pages/home.tsx +10 -15
- package/dist/templates/blog/pages/post.tsx +42 -14
- package/dist/templates/blog/pages/sitemap.ts +4 -5
- package/dist/templates/blog/pages/tag.tsx +8 -10
- 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 +3 -4
- package/dist/templates/corporate/pages/home.tsx +7 -12
- package/dist/templates/corporate/pages/post.tsx +20 -14
- package/dist/templates/corporate/pages/sitemap.ts +3 -4
- package/dist/templates/corporate/pages/tag.tsx +8 -10
- 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 +3 -4
- package/dist/templates/dads/pages/home.tsx +7 -12
- package/dist/templates/dads/pages/post.tsx +20 -14
- package/dist/templates/dads/pages/sitemap.ts +3 -4
- package/dist/templates/dads/pages/tag.tsx +8 -10
- 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 +3 -4
- package/dist/templates/docs/pages/home.tsx +6 -9
- package/dist/templates/docs/pages/post.tsx +19 -13
- package/dist/templates/docs/pages/sitemap.ts +3 -4
- package/dist/templates/docs/pages/tag.tsx +8 -10
- 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 +4 -5
- package/dist/templates/landing/pages/home.tsx +7 -12
- package/dist/templates/landing/pages/post.tsx +20 -14
- package/dist/templates/landing/pages/sitemap.ts +3 -4
- package/dist/templates/landing/pages/tag.tsx +8 -10
- 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 +4 -5
- package/dist/templates/minimal/pages/home.tsx +6 -8
- package/dist/templates/minimal/pages/post.tsx +19 -12
- package/dist/templates/minimal/pages/sitemap.ts +4 -5
- package/dist/templates/minimal/pages/tag.tsx +7 -8
- package/dist/templates/minimal/tokens.css +17 -39
- package/dist/templates/plugin-local/README.md +34 -0
- package/dist/templates/plugin-local/index.ts +39 -0
- package/dist/templates/plugin-standalone/CHANGELOG.md +1 -0
- package/dist/templates/plugin-standalone/README.ja.md +43 -0
- package/dist/templates/plugin-standalone/README.md +43 -0
- package/dist/templates/plugin-standalone/package.json +47 -0
- package/dist/templates/plugin-standalone/src/index.test.ts +16 -0
- package/dist/templates/plugin-standalone/src/index.ts +29 -0
- package/dist/templates/plugin-standalone/tsconfig.json +16 -0
- package/dist/templates/plugin-standalone/tsup.config.ts +8 -0
- 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/components/i18n-provider.tsx +0 -15
- package/dist/templates/_shared/lib/admin-site-client.ts +0 -10
- package/dist/templates/_shared/lib/admin-site.ts +0 -12
- package/dist/templates/_shared/lib/amplify-server.ts +0 -7
- package/dist/templates/_shared/lib/auth-server.ts +0 -15
- package/dist/templates/_shared/lib/cn.ts +0 -5
- package/dist/templates/_shared/lib/i18n.ts +0 -35
- package/dist/templates/_shared/lib/kv-provider.ts +0 -7
- package/dist/templates/_shared/lib/media.ts +0 -6
- package/dist/templates/_shared/lib/posts-provider.ts +0 -7
- package/dist/templates/_shared/lib/posts-public.ts +0 -27
- package/dist/templates/_shared/lib/posts.ts +0 -12
- package/dist/templates/_shared/lib/seo.ts +0 -11
- package/dist/templates/_shared/lib/site-settings.ts +0 -11
- package/dist/templates/_shared/lib/storage.ts +0 -10
- package/dist/templates/_shared/lib/theme-actions.ts +0 -5
- package/dist/templates/_shared/lib/theme-active.ts +0 -10
- package/dist/templates/_shared/lib/theme-config.ts +0 -12
- package/dist/templates/_shared/lib/upload.ts +0 -6
- /package/dist/templates/_shared/app/{site/[siteId]/[slug] → [slug]}/page.tsx +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/feed.xml → feed.xml}/route.ts +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/og → og}/[slug]/route.ts +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/page.tsx → page.tsx} +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/sitemap.xml → sitemap.xml}/route.ts +0 -0
- /package/dist/templates/_shared/app/{site/[siteId]/tag → tag}/[tag]/page.tsx +0 -0
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { spinner as spinner2, outro as
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { basename as
|
|
4
|
+
import { spinner as spinner2, outro as outro5, log as log7 } from "@clack/prompts";
|
|
5
|
+
import { existsSync as existsSync9 } from "fs";
|
|
6
|
+
import { basename as basename4, resolve as resolve5 } from "path";
|
|
7
7
|
|
|
8
8
|
// src/prompts.ts
|
|
9
9
|
import * as p from "@clack/prompts";
|
|
@@ -106,6 +106,9 @@ function sharedTemplateDir() {
|
|
|
106
106
|
function templatePath(theme) {
|
|
107
107
|
return resolve(templatesDir, theme);
|
|
108
108
|
}
|
|
109
|
+
function pluginTemplateDir(mode) {
|
|
110
|
+
return resolve(templatesDir, mode === "local" ? "plugin-local" : "plugin-standalone");
|
|
111
|
+
}
|
|
109
112
|
|
|
110
113
|
// src/gitignore.ts
|
|
111
114
|
var DEFAULT_GITIGNORE = `# Dependencies
|
|
@@ -269,6 +272,20 @@ async function scaffold(sharedDir, themesRoot, destDir, opts) {
|
|
|
269
272
|
// src/args.ts
|
|
270
273
|
var VALID_THEMES = ["blog", "minimal", "landing", "corporate", "docs", "dads"];
|
|
271
274
|
var VALID_PLUGINS = ["seo", "rss", "webhook"];
|
|
275
|
+
var VALID_PLUGIN_TRUST_LEVELS = [
|
|
276
|
+
"untrusted",
|
|
277
|
+
"trusted",
|
|
278
|
+
"privileged"
|
|
279
|
+
];
|
|
280
|
+
var VALID_PLUGIN_CAPABILITIES = [
|
|
281
|
+
"publicHead",
|
|
282
|
+
"publicBody",
|
|
283
|
+
"metadata",
|
|
284
|
+
"eventHooks",
|
|
285
|
+
"adminSettings",
|
|
286
|
+
"writePublicAsset",
|
|
287
|
+
"schema"
|
|
288
|
+
];
|
|
272
289
|
var STRING_FLAGS = /* @__PURE__ */ new Set([
|
|
273
290
|
"--site-name",
|
|
274
291
|
"--themes",
|
|
@@ -279,7 +296,11 @@ var STRING_FLAGS = /* @__PURE__ */ new Set([
|
|
|
279
296
|
"--aws-region",
|
|
280
297
|
"--domain",
|
|
281
298
|
"--subdomain",
|
|
282
|
-
"--iam-service-role"
|
|
299
|
+
"--iam-service-role",
|
|
300
|
+
// Phase 5 `plugin <name>` flags
|
|
301
|
+
"--trust-level",
|
|
302
|
+
"--capabilities",
|
|
303
|
+
"--description"
|
|
283
304
|
]);
|
|
284
305
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
285
306
|
"--deploy",
|
|
@@ -291,7 +312,12 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
|
291
312
|
"--create-iam-role",
|
|
292
313
|
"--skip-confirm",
|
|
293
314
|
"--help",
|
|
294
|
-
"-h"
|
|
315
|
+
"-h",
|
|
316
|
+
// Phase 5 `plugin <name>` mode flags
|
|
317
|
+
"--local",
|
|
318
|
+
"--standalone",
|
|
319
|
+
// setup-encryption-key flags
|
|
320
|
+
"--gitignore"
|
|
295
321
|
]);
|
|
296
322
|
function parseDeployArgs(argv) {
|
|
297
323
|
const out = {
|
|
@@ -299,6 +325,9 @@ function parseDeployArgs(argv) {
|
|
|
299
325
|
mount: false,
|
|
300
326
|
upgrade: false,
|
|
301
327
|
copyTheme: false,
|
|
328
|
+
createPlugin: false,
|
|
329
|
+
setupEncryptionKey: false,
|
|
330
|
+
gitignore: false,
|
|
302
331
|
dryRun: false,
|
|
303
332
|
noInstall: false,
|
|
304
333
|
githubPrivate: false,
|
|
@@ -346,6 +375,21 @@ function parseDeployArgs(argv) {
|
|
|
346
375
|
case "-h":
|
|
347
376
|
out.help = true;
|
|
348
377
|
break;
|
|
378
|
+
case "--local":
|
|
379
|
+
if (out.pluginMode === "standalone") {
|
|
380
|
+
throw new Error("Cannot combine --local and --standalone");
|
|
381
|
+
}
|
|
382
|
+
out.pluginMode = "local";
|
|
383
|
+
break;
|
|
384
|
+
case "--standalone":
|
|
385
|
+
if (out.pluginMode === "local") {
|
|
386
|
+
throw new Error("Cannot combine --local and --standalone");
|
|
387
|
+
}
|
|
388
|
+
out.pluginMode = "standalone";
|
|
389
|
+
break;
|
|
390
|
+
case "--gitignore":
|
|
391
|
+
out.gitignore = true;
|
|
392
|
+
break;
|
|
349
393
|
}
|
|
350
394
|
continue;
|
|
351
395
|
}
|
|
@@ -401,6 +445,31 @@ function parseDeployArgs(argv) {
|
|
|
401
445
|
case "--iam-service-role":
|
|
402
446
|
out.iamServiceRole = value;
|
|
403
447
|
break;
|
|
448
|
+
case "--trust-level": {
|
|
449
|
+
if (!VALID_PLUGIN_TRUST_LEVELS.includes(value)) {
|
|
450
|
+
throw new Error(
|
|
451
|
+
`Invalid --trust-level "${value}". Valid values: ${VALID_PLUGIN_TRUST_LEVELS.join(", ")}`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
out.pluginTrustLevel = value;
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case "--capabilities": {
|
|
458
|
+
const caps = value.split(",").map((c) => c.trim()).filter(Boolean);
|
|
459
|
+
const invalid = caps.filter(
|
|
460
|
+
(c) => !VALID_PLUGIN_CAPABILITIES.includes(c)
|
|
461
|
+
);
|
|
462
|
+
if (invalid.length > 0) {
|
|
463
|
+
throw new Error(
|
|
464
|
+
`Invalid capability(ies): ${invalid.join(", ")}. Valid values: ${VALID_PLUGIN_CAPABILITIES.join(", ")}`
|
|
465
|
+
);
|
|
466
|
+
}
|
|
467
|
+
out.pluginCapabilities = caps;
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
case "--description":
|
|
471
|
+
out.pluginDescription = value;
|
|
472
|
+
break;
|
|
404
473
|
}
|
|
405
474
|
continue;
|
|
406
475
|
}
|
|
@@ -412,6 +481,10 @@ function parseDeployArgs(argv) {
|
|
|
412
481
|
out.upgrade = true;
|
|
413
482
|
continue;
|
|
414
483
|
}
|
|
484
|
+
if (raw === "setup-encryption-key" && out.projectName === void 0 && !out.setupEncryptionKey && !out.upgrade && !out.copyTheme && !out.createPlugin) {
|
|
485
|
+
out.setupEncryptionKey = true;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
415
488
|
if (raw === "copy-theme" && out.projectName === void 0 && !out.copyTheme) {
|
|
416
489
|
out.copyTheme = true;
|
|
417
490
|
continue;
|
|
@@ -424,6 +497,14 @@ function parseDeployArgs(argv) {
|
|
|
424
497
|
out.copyThemeTarget = raw;
|
|
425
498
|
continue;
|
|
426
499
|
}
|
|
500
|
+
if (raw === "plugin" && out.projectName === void 0 && !out.createPlugin && !out.upgrade && !out.copyTheme) {
|
|
501
|
+
out.createPlugin = true;
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
if (out.createPlugin && out.pluginName === void 0) {
|
|
505
|
+
out.pluginName = raw;
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
427
508
|
if (out.projectName === void 0) {
|
|
428
509
|
out.projectName = raw;
|
|
429
510
|
} else {
|
|
@@ -435,9 +516,12 @@ function parseDeployArgs(argv) {
|
|
|
435
516
|
var HELP_TEXT = `create-ampless \u2014 scaffold an ampless project
|
|
436
517
|
|
|
437
518
|
Usage:
|
|
438
|
-
npx create-ampless@
|
|
439
|
-
npx create-ampless@
|
|
440
|
-
npx create-ampless@
|
|
519
|
+
npx create-ampless@latest <project-name> [options]
|
|
520
|
+
npx create-ampless@latest --mount [options] # in an existing project dir
|
|
521
|
+
npx create-ampless@latest upgrade [options] # in an existing project dir
|
|
522
|
+
npx create-ampless@latest copy-theme <src> <dst> # in an existing project dir
|
|
523
|
+
npx create-ampless@latest plugin <name> [options] # scaffold a plugin (Phase 5)
|
|
524
|
+
npx create-ampless@latest setup-encryption-key [--gitignore] # generate encryption key file
|
|
441
525
|
|
|
442
526
|
Options:
|
|
443
527
|
--site-name <name> Site display name (default: "My Blog")
|
|
@@ -487,7 +571,61 @@ copy-theme <source> <target>
|
|
|
487
571
|
(the convention that flags it as user-owned, so upgrade leaves
|
|
488
572
|
it alone). Run inside an existing ampless project.
|
|
489
573
|
|
|
490
|
-
Example: npx create-ampless@
|
|
574
|
+
Example: npx create-ampless@latest copy-theme blog my-blog
|
|
575
|
+
|
|
576
|
+
plugin <name>
|
|
577
|
+
Scaffold an ampless plugin. Two modes:
|
|
578
|
+
|
|
579
|
+
--local Default. Writes plugins/<name>/index.ts inside
|
|
580
|
+
the current ampless site. The site itself is the
|
|
581
|
+
build / publish unit; the plugin file is just
|
|
582
|
+
code that sits there. Plugin name is a kebab-case
|
|
583
|
+
identifier (e.g. "site-verification").
|
|
584
|
+
|
|
585
|
+
--standalone Writes a complete npm package at ./<name>/ ready
|
|
586
|
+
for 'npm publish'. Plugin name should be the npm
|
|
587
|
+
package name (e.g. "@scope/ampless-plugin-foo"
|
|
588
|
+
or "ampless-plugin-foo"); the kebab segment
|
|
589
|
+
becomes the AmplessPlugin.name.
|
|
590
|
+
|
|
591
|
+
--trust-level <value> 'untrusted' (default), 'trusted', or 'privileged'
|
|
592
|
+
--capabilities <list> Comma-separated. Valid: publicHead, publicBody,
|
|
593
|
+
metadata, eventHooks, adminSettings,
|
|
594
|
+
writePublicAsset, schema
|
|
595
|
+
(default: publicHead,adminSettings)
|
|
596
|
+
--description "<text>" Optional one-line description
|
|
597
|
+
|
|
598
|
+
Examples:
|
|
599
|
+
npx create-ampless@latest plugin site-verification
|
|
600
|
+
npx create-ampless@latest plugin @ishinao/ampless-plugin-foo --standalone
|
|
601
|
+
|
|
602
|
+
setup-encryption-key
|
|
603
|
+
Generate the AES-256-GCM encryption key for plugin secret storage
|
|
604
|
+
and write it to amplify/secrets/encryption-key.ts (adjacent to
|
|
605
|
+
amplify/backend.ts). No AWS credentials required \u2014 this is a
|
|
606
|
+
local file operation only.
|
|
607
|
+
|
|
608
|
+
After generating, import the constant in amplify/backend.ts and
|
|
609
|
+
pass it to defineAmplessBackend({ pluginSecretEncryptionKey: ... }).
|
|
610
|
+
Then redeploy (or restart the sandbox) to inject the key into the
|
|
611
|
+
Lambda env vars.
|
|
612
|
+
|
|
613
|
+
--gitignore Add amplify/secrets/encryption-key.ts to
|
|
614
|
+
.gitignore. Default: file is committed
|
|
615
|
+
(safe for private repos). Use this flag only
|
|
616
|
+
for public repos; distribute the key separately.
|
|
617
|
+
|
|
618
|
+
Threat model:
|
|
619
|
+
\u2713 DDB Console operator sees ciphertext only
|
|
620
|
+
\u26A0 Source repo / deploy artifact access defeats encryption
|
|
621
|
+
\u2717 Malicious trusted plugin in the same Lambda can also read
|
|
622
|
+
the key (per-plugin Lambda isolation = roadmap)
|
|
623
|
+
|
|
624
|
+
Rotation: re-run with confirm overwrite. Existing ciphertext
|
|
625
|
+
becomes unreadable; re-save each secret via /admin/plugins.
|
|
626
|
+
|
|
627
|
+
Example: npx create-ampless@latest setup-encryption-key
|
|
628
|
+
Example: npx create-ampless@latest setup-encryption-key --gitignore
|
|
491
629
|
`;
|
|
492
630
|
|
|
493
631
|
// src/deploy-prompts.ts
|
|
@@ -1091,38 +1229,19 @@ function splitDomain(domain, subdomain) {
|
|
|
1091
1229
|
async function rewriteCmsConfigForDomain(projectDir, fullDomain) {
|
|
1092
1230
|
const path = resolve4(projectDir, "cms.config.ts");
|
|
1093
1231
|
if (!existsSync4(path)) {
|
|
1094
|
-
return { urlRewritten: false
|
|
1232
|
+
return { urlRewritten: false };
|
|
1095
1233
|
}
|
|
1096
1234
|
let content = await readFile2(path, "utf-8");
|
|
1097
1235
|
let urlRewritten = false;
|
|
1098
|
-
let sitesInjected = false;
|
|
1099
1236
|
const urlRe = /url:\s*['"]http:\/\/localhost:3000['"]/;
|
|
1100
1237
|
if (urlRe.test(content)) {
|
|
1101
1238
|
content = content.replace(urlRe, `url: 'https://${fullDomain}'`);
|
|
1102
1239
|
urlRewritten = true;
|
|
1103
1240
|
}
|
|
1104
|
-
|
|
1105
|
-
if (!sitesActiveRe.test(content)) {
|
|
1106
|
-
const siteCloseRe = /(\n(\s*)site:\s*\{[\s\S]*?\n\2\},)/;
|
|
1107
|
-
const m = siteCloseRe.exec(content);
|
|
1108
|
-
if (m) {
|
|
1109
|
-
const indent = m[2] ?? " ";
|
|
1110
|
-
const inner = indent + " ";
|
|
1111
|
-
const innermost = inner + " ";
|
|
1112
|
-
const inject = `
|
|
1113
|
-
${indent}sites: {
|
|
1114
|
-
${inner}default: {
|
|
1115
|
-
${innermost}domains: ['${fullDomain}'],
|
|
1116
|
-
${inner}},
|
|
1117
|
-
${indent}},`;
|
|
1118
|
-
content = content.replace(siteCloseRe, `$1${inject}`);
|
|
1119
|
-
sitesInjected = true;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
if (urlRewritten || sitesInjected) {
|
|
1241
|
+
if (urlRewritten) {
|
|
1123
1242
|
await writeFile2(path, content, "utf-8");
|
|
1124
1243
|
}
|
|
1125
|
-
return { urlRewritten
|
|
1244
|
+
return { urlRewritten };
|
|
1126
1245
|
}
|
|
1127
1246
|
async function run(cmd, args, opts = {}) {
|
|
1128
1247
|
const { step, ...execaOpts } = opts;
|
|
@@ -1503,11 +1622,8 @@ async function runDeploy(opts) {
|
|
|
1503
1622
|
const { registrable, subdomain } = splitDomain(opts.domain, opts.subdomain);
|
|
1504
1623
|
const fullDomain = subdomain ? `${subdomain}.${registrable}` : registrable;
|
|
1505
1624
|
const mutations = await rewriteCmsConfigForDomain(opts.projectDir, fullDomain);
|
|
1506
|
-
if (mutations.urlRewritten
|
|
1507
|
-
|
|
1508
|
-
if (mutations.urlRewritten) parts.push(`site.url \u2192 https://${fullDomain}`);
|
|
1509
|
-
if (mutations.sitesInjected) parts.push(`sites.default.domains += ${fullDomain}`);
|
|
1510
|
-
log.info(`cms.config.ts updated: ${parts.join(", ")}`);
|
|
1625
|
+
if (mutations.urlRewritten) {
|
|
1626
|
+
log.info(`cms.config.ts updated: site.url \u2192 https://${fullDomain}`);
|
|
1511
1627
|
}
|
|
1512
1628
|
}
|
|
1513
1629
|
const created = {};
|
|
@@ -1761,6 +1877,15 @@ import { join as join3, relative, extname as extname2, dirname } from "path";
|
|
|
1761
1877
|
import { log as log3, outro } from "@clack/prompts";
|
|
1762
1878
|
import pc3 from "picocolors";
|
|
1763
1879
|
import { execa as execa4 } from "execa";
|
|
1880
|
+
var AMPLESS_MANAGED_APP_PATHS = [
|
|
1881
|
+
"app/(admin)/admin",
|
|
1882
|
+
"app/api/admin",
|
|
1883
|
+
"app/api/media",
|
|
1884
|
+
"app/api/mcp",
|
|
1885
|
+
"app/login",
|
|
1886
|
+
"app/site"
|
|
1887
|
+
];
|
|
1888
|
+
var AMPLESS_RETIRED_PATHS = [];
|
|
1764
1889
|
var AMPLESS_PACKAGES = /* @__PURE__ */ new Set([
|
|
1765
1890
|
"ampless",
|
|
1766
1891
|
"@ampless/admin",
|
|
@@ -1768,8 +1893,14 @@ var AMPLESS_PACKAGES = /* @__PURE__ */ new Set([
|
|
|
1768
1893
|
"@ampless/runtime",
|
|
1769
1894
|
"@ampless/plugin-seo",
|
|
1770
1895
|
"@ampless/plugin-rss",
|
|
1896
|
+
"@ampless/plugin-schema-jsonld",
|
|
1771
1897
|
"@ampless/plugin-webhook",
|
|
1772
|
-
"@ampless/plugin-og-image"
|
|
1898
|
+
"@ampless/plugin-og-image",
|
|
1899
|
+
"@ampless/plugin-analytics-ga4",
|
|
1900
|
+
"@ampless/plugin-gtm",
|
|
1901
|
+
"@ampless/plugin-plausible",
|
|
1902
|
+
"@ampless/plugin-cookie-consent",
|
|
1903
|
+
"@ampless/plugin-reading-time"
|
|
1773
1904
|
]);
|
|
1774
1905
|
var AMPLESS_MANAGED_SCRIPTS = /* @__PURE__ */ new Set([
|
|
1775
1906
|
"sandbox",
|
|
@@ -1791,15 +1922,34 @@ var PROTECTED_PATTERNS = [
|
|
|
1791
1922
|
/^\.amplify(\/|$)/,
|
|
1792
1923
|
/^amplify_outputs\.json$/,
|
|
1793
1924
|
/^next-env\.d\.ts$/,
|
|
1925
|
+
// `tsconfig.json` is auto-managed by Next.js during `next build` /
|
|
1926
|
+
// `next dev` — it rewrites `jsx: "preserve"` → `"react-jsx"` for
|
|
1927
|
+
// the React automatic runtime and appends `.next/dev/types/**/*.ts`
|
|
1928
|
+
// to `include`. Treating the file as `replace` here would overwrite
|
|
1929
|
+
// those mutations on every `update-ampless`, only for Next.js to
|
|
1930
|
+
// re-apply them on the next build — a churn cycle that produces a
|
|
1931
|
+
// dirty diff after every upgrade. Protect the file; users hand-
|
|
1932
|
+
// merge new compiler options in the rare case the template
|
|
1933
|
+
// tsconfig changes meaningfully.
|
|
1934
|
+
/^tsconfig\.json$/,
|
|
1794
1935
|
/^tsconfig\.tsbuildinfo$/,
|
|
1795
1936
|
/^pnpm-lock\.yaml$/,
|
|
1796
1937
|
/^package-lock\.json$/,
|
|
1797
1938
|
// Anything under themes/ is handled by syncThemes; the file-walk
|
|
1798
1939
|
// classifier must not double-process it.
|
|
1799
1940
|
/^themes(\/|$)/,
|
|
1800
|
-
/^themes-registry\.ts
|
|
1941
|
+
/^themes-registry\.ts$/,
|
|
1942
|
+
// `plugins/` is the site-local plugin directory (user-owned, mirrors
|
|
1943
|
+
// the `themes/my-*` convention for user-customised themes). The
|
|
1944
|
+
// initial scaffold seeds a README into this directory; everything
|
|
1945
|
+
// afterwards is the user's territory. We never overwrite or delete
|
|
1946
|
+
// files here — even the seeded README is frozen at scaffold time,
|
|
1947
|
+
// because plugin authors will sometimes edit it to document the
|
|
1948
|
+
// site's own conventions. The kept-up-to-date "how to write a
|
|
1949
|
+
// plugin" doc lives in `packages/ampless/docs/plugin-author-guide.md`.
|
|
1950
|
+
/^plugins(\/|$)/
|
|
1801
1951
|
];
|
|
1802
|
-
var SEED_IF_MISSING_PATTERN = /\.custom\.ts$/;
|
|
1952
|
+
var SEED_IF_MISSING_PATTERN = /\.custom\.ts$|^plugins\/README(\.ja)?\.md$|^amplify\/secrets\/encryption-key\.ts$/;
|
|
1803
1953
|
var TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
1804
1954
|
".json",
|
|
1805
1955
|
".md",
|
|
@@ -1819,12 +1969,24 @@ var TEXT_EXTENSIONS2 = /* @__PURE__ */ new Set([
|
|
|
1819
1969
|
".gitignore"
|
|
1820
1970
|
]);
|
|
1821
1971
|
function isProtected(relPath) {
|
|
1972
|
+
if (SEED_IF_MISSING_PATTERN.test(relPath)) return false;
|
|
1822
1973
|
return PROTECTED_PATTERNS.some((re) => re.test(relPath));
|
|
1823
1974
|
}
|
|
1975
|
+
var NON_THEME_TEMPLATE_PREFIXES = ["plugin-"];
|
|
1976
|
+
function isThemeDirName(name) {
|
|
1977
|
+
if (name === "_shared") return false;
|
|
1978
|
+
for (const prefix of NON_THEME_TEMPLATE_PREFIXES) {
|
|
1979
|
+
if (name.startsWith(prefix)) return false;
|
|
1980
|
+
}
|
|
1981
|
+
return true;
|
|
1982
|
+
}
|
|
1983
|
+
function isQuarantinedThemeName(name) {
|
|
1984
|
+
return !isThemeDirName(name);
|
|
1985
|
+
}
|
|
1824
1986
|
async function listShippedThemes(templatesRoot) {
|
|
1825
1987
|
if (!existsSync5(templatesRoot)) return [];
|
|
1826
1988
|
const entries = await readdir3(templatesRoot, { withFileTypes: true });
|
|
1827
|
-
return entries.filter((e) => e.isDirectory() && e.name
|
|
1989
|
+
return entries.filter((e) => e.isDirectory() && isThemeDirName(e.name)).map((e) => e.name).sort();
|
|
1828
1990
|
}
|
|
1829
1991
|
var USER_OWNED_THEME_FILES = /* @__PURE__ */ new Set(["README.md", ".gitignore"]);
|
|
1830
1992
|
function isUserOwnedThemeFile(path) {
|
|
@@ -1852,6 +2014,16 @@ async function syncThemes(destDir, templatesRoot) {
|
|
|
1852
2014
|
const shipped = await listShippedThemes(templatesRoot);
|
|
1853
2015
|
const themesDir = join3(destDir, "themes");
|
|
1854
2016
|
await mkdir2(themesDir, { recursive: true });
|
|
2017
|
+
const quarantined = [];
|
|
2018
|
+
if (existsSync5(themesDir)) {
|
|
2019
|
+
const present = await readdir3(themesDir, { withFileTypes: true });
|
|
2020
|
+
for (const entry of present) {
|
|
2021
|
+
if (!entry.isDirectory()) continue;
|
|
2022
|
+
if (!isQuarantinedThemeName(entry.name)) continue;
|
|
2023
|
+
await rm2(join3(themesDir, entry.name), { recursive: true, force: true });
|
|
2024
|
+
quarantined.push(entry.name);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
1855
2027
|
for (const name of shipped) {
|
|
1856
2028
|
const src = join3(templatesRoot, name);
|
|
1857
2029
|
const dst = join3(themesDir, name);
|
|
@@ -1865,10 +2037,12 @@ async function syncThemes(destDir, templatesRoot) {
|
|
|
1865
2037
|
}
|
|
1866
2038
|
const installed = await discoverInstalledThemes(destDir);
|
|
1867
2039
|
const shippedSet = new Set(shipped);
|
|
1868
|
-
const preserved = installed.filter(
|
|
2040
|
+
const preserved = installed.filter(
|
|
2041
|
+
(t) => !shippedSet.has(t) && !isQuarantinedThemeName(t)
|
|
2042
|
+
);
|
|
1869
2043
|
const all = [...shipped, ...preserved];
|
|
1870
2044
|
await writeFile3(join3(destDir, "themes-registry.ts"), buildRegistry(all), "utf-8");
|
|
1871
|
-
return { synced: shipped, preserved };
|
|
2045
|
+
return { synced: shipped, preserved, quarantined };
|
|
1872
2046
|
}
|
|
1873
2047
|
async function walkDir(dir, base, out) {
|
|
1874
2048
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
@@ -1882,6 +2056,69 @@ async function walkDir(dir, base, out) {
|
|
|
1882
2056
|
}
|
|
1883
2057
|
}
|
|
1884
2058
|
}
|
|
2059
|
+
async function listFilesRecursive(root) {
|
|
2060
|
+
const out = [];
|
|
2061
|
+
async function walk(current, relPrefix) {
|
|
2062
|
+
const entries = await readdir3(current, { withFileTypes: true });
|
|
2063
|
+
for (const entry of entries) {
|
|
2064
|
+
const rel = relPrefix ? `${relPrefix}/${entry.name}` : entry.name;
|
|
2065
|
+
if (entry.isDirectory()) {
|
|
2066
|
+
await walk(join3(current, entry.name), rel);
|
|
2067
|
+
} else if (entry.isFile()) {
|
|
2068
|
+
out.push(rel);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
if (!existsSync5(root)) return out;
|
|
2073
|
+
await walk(root, "");
|
|
2074
|
+
return out;
|
|
2075
|
+
}
|
|
2076
|
+
async function pruneEmptyDirs(root) {
|
|
2077
|
+
if (!existsSync5(root)) return;
|
|
2078
|
+
const entries = await readdir3(root, { withFileTypes: true });
|
|
2079
|
+
for (const entry of entries) {
|
|
2080
|
+
if (entry.isDirectory()) {
|
|
2081
|
+
await pruneEmptyDirs(join3(root, entry.name));
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
const remaining = await readdir3(root);
|
|
2085
|
+
if (remaining.length === 0) {
|
|
2086
|
+
await rm2(root, { recursive: true, force: true });
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
async function findObsoleteFiles(destDir, sharedDir) {
|
|
2090
|
+
const obsolete = [];
|
|
2091
|
+
for (const managedPath of AMPLESS_MANAGED_APP_PATHS) {
|
|
2092
|
+
const userPath = join3(destDir, managedPath);
|
|
2093
|
+
if (!existsSync5(userPath)) continue;
|
|
2094
|
+
const templatePath2 = join3(sharedDir, managedPath);
|
|
2095
|
+
const templateFiles = new Set(await listFilesRecursive(templatePath2));
|
|
2096
|
+
const userFiles = await listFilesRecursive(userPath);
|
|
2097
|
+
for (const f of userFiles) {
|
|
2098
|
+
if (!templateFiles.has(f)) {
|
|
2099
|
+
obsolete.push(`${managedPath}/${f}`);
|
|
2100
|
+
}
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
for (const retiredPath of AMPLESS_RETIRED_PATHS) {
|
|
2104
|
+
const userFile = join3(destDir, retiredPath);
|
|
2105
|
+
if (existsSync5(userFile)) {
|
|
2106
|
+
obsolete.push(retiredPath);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
return obsolete;
|
|
2110
|
+
}
|
|
2111
|
+
async function removeObsoleteFiles(destDir, paths) {
|
|
2112
|
+
for (const rel of paths) {
|
|
2113
|
+
const abs = join3(destDir, rel);
|
|
2114
|
+
if (existsSync5(abs)) {
|
|
2115
|
+
await rm2(abs, { force: true });
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
for (const managedPath of AMPLESS_MANAGED_APP_PATHS) {
|
|
2119
|
+
await pruneEmptyDirs(join3(destDir, managedPath));
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
1885
2122
|
function substituteVars(content, vars) {
|
|
1886
2123
|
return content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
1887
2124
|
}
|
|
@@ -1934,22 +2171,34 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
1934
2171
|
const seedSkipped = classification.seed.filter((r) => existsSync5(join3(destDir, r)));
|
|
1935
2172
|
const shippedThemes = themeSyncEnabled ? await listShippedThemes(templatesRoot) : [];
|
|
1936
2173
|
const existingThemes = themeSyncEnabled ? await discoverInstalledThemes(destDir) : [];
|
|
1937
|
-
const
|
|
2174
|
+
const quarantinedThemesPreview = existingThemes.filter(isQuarantinedThemeName);
|
|
2175
|
+
const preservedThemes = existingThemes.filter(
|
|
2176
|
+
(t) => !shippedThemes.includes(t) && !isQuarantinedThemeName(t)
|
|
2177
|
+
);
|
|
2178
|
+
const obsoleteFiles = await findObsoleteFiles(destDir, sharedDir);
|
|
1938
2179
|
log3.info(
|
|
1939
|
-
`replace: ${pc3.green(
|
|
2180
|
+
`replace: ${pc3.green(`${replaceNew.length} added`)} / ${pc3.yellow(`${replaceUpdate.length} updated`)}`
|
|
1940
2181
|
);
|
|
1941
|
-
log3.info(`merge: ${pc3.cyan("package.json: ampless deps
|
|
2182
|
+
log3.info(`merge: ${pc3.cyan("package.json: sync ampless deps and managed scripts with the template")}`);
|
|
1942
2183
|
if (classification.seed.length > 0) {
|
|
1943
2184
|
log3.info(
|
|
1944
|
-
`seed: ${pc3.green(
|
|
2185
|
+
`seed: ${pc3.green(`${seedNew.length} added`)} / ${pc3.dim(`${seedSkipped.length} kept (existing *.custom.ts left untouched)`)}`
|
|
1945
2186
|
);
|
|
1946
2187
|
}
|
|
1947
2188
|
if (themeSyncEnabled) {
|
|
1948
2189
|
log3.info(
|
|
1949
|
-
`themes: ${pc3.cyan(
|
|
2190
|
+
`themes: ${pc3.cyan(`${shippedThemes.length} default themes synced`)} / ${pc3.dim(`${preservedThemes.length} custom (my-*) themes preserved`)}`
|
|
1950
2191
|
);
|
|
2192
|
+
if (quarantinedThemesPreview.length > 0) {
|
|
2193
|
+
log3.info(
|
|
2194
|
+
`recover: ${pc3.yellow(`${quarantinedThemesPreview.length} bogus theme dir(s) removed`)} (${quarantinedThemesPreview.join(", ")} \u2014 scaffold templates leaked in by an earlier buggy create-ampless@alpha)`
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
if (obsoleteFiles.length > 0) {
|
|
2199
|
+
log3.info(`cleanup: ${pc3.yellow(`${obsoleteFiles.length} removed`)} (files under ampless-managed app/ paths that no longer exist in the template)`);
|
|
1951
2200
|
}
|
|
1952
|
-
log3.info(`protected: ${pc3.dim(
|
|
2201
|
+
log3.info(`protected: ${pc3.dim(`${classification.protected.length} template files left untouched`)}`);
|
|
1953
2202
|
if (opts.dryRun) {
|
|
1954
2203
|
return {
|
|
1955
2204
|
added: replaceNew,
|
|
@@ -1958,7 +2207,9 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
1958
2207
|
protected: classification.protected,
|
|
1959
2208
|
themesSynced: shippedThemes,
|
|
1960
2209
|
themesPreserved: preservedThemes,
|
|
1961
|
-
|
|
2210
|
+
themesQuarantined: quarantinedThemesPreview,
|
|
2211
|
+
packageJsonMerged: false,
|
|
2212
|
+
obsoleteRemoved: obsoleteFiles
|
|
1962
2213
|
};
|
|
1963
2214
|
}
|
|
1964
2215
|
for (const rel of classification.replace) {
|
|
@@ -1971,7 +2222,8 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
1971
2222
|
const dst = join3(destDir, rel);
|
|
1972
2223
|
await copyWithSubstitution(src, dst, vars);
|
|
1973
2224
|
}
|
|
1974
|
-
const themeResult = themeSyncEnabled ? await syncThemes(destDir, templatesRoot) : { synced: [], preserved: [] };
|
|
2225
|
+
const themeResult = themeSyncEnabled ? await syncThemes(destDir, templatesRoot) : { synced: [], preserved: [], quarantined: [] };
|
|
2226
|
+
await removeObsoleteFiles(destDir, obsoleteFiles);
|
|
1975
2227
|
const templatePkgRaw = await readFile3(join3(sharedDir, "package.json"), "utf-8");
|
|
1976
2228
|
const templatePkg = JSON.parse(templatePkgRaw);
|
|
1977
2229
|
const indent = detectIndent(projectPkgRaw);
|
|
@@ -1988,9 +2240,12 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
1988
2240
|
}
|
|
1989
2241
|
const templateScripts = templatePkg["scripts"] ?? {};
|
|
1990
2242
|
const projectScripts = projectPkg["scripts"] ?? {};
|
|
1991
|
-
for (const
|
|
1992
|
-
if (
|
|
1993
|
-
|
|
2243
|
+
for (const name of AMPLESS_MANAGED_SCRIPTS) {
|
|
2244
|
+
if (name in templateScripts) {
|
|
2245
|
+
projectScripts[name] = templateScripts[name];
|
|
2246
|
+
} else {
|
|
2247
|
+
delete projectScripts[name];
|
|
2248
|
+
}
|
|
1994
2249
|
}
|
|
1995
2250
|
if (Object.keys(projectScripts).length > 0) {
|
|
1996
2251
|
projectPkg["scripts"] = projectScripts;
|
|
@@ -2008,7 +2263,9 @@ async function runUpgradeIn(destDir, sharedDir, opts = {}) {
|
|
|
2008
2263
|
protected: classification.protected,
|
|
2009
2264
|
themesSynced: themeResult.synced,
|
|
2010
2265
|
themesPreserved: themeResult.preserved,
|
|
2011
|
-
|
|
2266
|
+
themesQuarantined: themeResult.quarantined,
|
|
2267
|
+
packageJsonMerged: true,
|
|
2268
|
+
obsoleteRemoved: obsoleteFiles
|
|
2012
2269
|
};
|
|
2013
2270
|
}
|
|
2014
2271
|
async function runUpgrade(args) {
|
|
@@ -2104,8 +2361,8 @@ async function runCopyTheme(args) {
|
|
|
2104
2361
|
const source = args.copyThemeSource;
|
|
2105
2362
|
const target = args.copyThemeTarget;
|
|
2106
2363
|
if (!source || !target) {
|
|
2107
|
-
log4.error("Usage: npx create-ampless@
|
|
2108
|
-
log4.info("Example: npx create-ampless@
|
|
2364
|
+
log4.error("Usage: npx create-ampless@latest copy-theme <source> <target>");
|
|
2365
|
+
log4.info("Example: npx create-ampless@latest copy-theme blog my-blog");
|
|
2109
2366
|
process.exit(1);
|
|
2110
2367
|
}
|
|
2111
2368
|
try {
|
|
@@ -2118,7 +2375,7 @@ async function runCopyTheme(args) {
|
|
|
2118
2375
|
|
|
2119
2376
|
themes-registry.ts updated. Next:
|
|
2120
2377
|
${pc4.cyan(`Open themes/${result.target}/ and start customising`)}
|
|
2121
|
-
${pc4.cyan(`Activate via /admin/sites
|
|
2378
|
+
${pc4.cyan(`Activate via /admin/sites/default/theme`)}`
|
|
2122
2379
|
);
|
|
2123
2380
|
} catch (err) {
|
|
2124
2381
|
log4.error(err instanceof Error ? err.message : String(err));
|
|
@@ -2126,12 +2383,407 @@ async function runCopyTheme(args) {
|
|
|
2126
2383
|
}
|
|
2127
2384
|
}
|
|
2128
2385
|
|
|
2129
|
-
// src/
|
|
2386
|
+
// src/plugin.ts
|
|
2387
|
+
import { cp as cp4, mkdir as mkdir3, readFile as readFile5, writeFile as writeFile5, readdir as readdir5 } from "fs/promises";
|
|
2388
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2389
|
+
import { join as join5, extname as extname3, basename as basename3 } from "path";
|
|
2390
|
+
import {
|
|
2391
|
+
log as log5,
|
|
2392
|
+
outro as outro3,
|
|
2393
|
+
text as text3,
|
|
2394
|
+
select as select2,
|
|
2395
|
+
multiselect as multiselect2,
|
|
2396
|
+
isCancel as isCancel3,
|
|
2397
|
+
cancel as cancel3
|
|
2398
|
+
} from "@clack/prompts";
|
|
2130
2399
|
import pc5 from "picocolors";
|
|
2400
|
+
var SCAFFOLD_AMPLESS_VERSION = "1.0.0-alpha.22";
|
|
2401
|
+
var TEXT_EXTENSIONS3 = /* @__PURE__ */ new Set([
|
|
2402
|
+
".json",
|
|
2403
|
+
".md",
|
|
2404
|
+
".ts",
|
|
2405
|
+
".tsx",
|
|
2406
|
+
".js",
|
|
2407
|
+
".jsx",
|
|
2408
|
+
".mjs",
|
|
2409
|
+
".cjs",
|
|
2410
|
+
".html",
|
|
2411
|
+
".css",
|
|
2412
|
+
".env",
|
|
2413
|
+
".txt",
|
|
2414
|
+
".yaml",
|
|
2415
|
+
".yml",
|
|
2416
|
+
".toml",
|
|
2417
|
+
".gitignore"
|
|
2418
|
+
]);
|
|
2419
|
+
function toKebab(s) {
|
|
2420
|
+
return s.replace(/[A-Z]/g, (c) => "-" + c.toLowerCase()).replace(/^-/, "");
|
|
2421
|
+
}
|
|
2422
|
+
function toCamelCase(kebab) {
|
|
2423
|
+
return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
2424
|
+
}
|
|
2425
|
+
function toPascalCase(kebab) {
|
|
2426
|
+
const c = toCamelCase(kebab);
|
|
2427
|
+
return c.charAt(0).toUpperCase() + c.slice(1);
|
|
2428
|
+
}
|
|
2429
|
+
function toDisplayName(kebab) {
|
|
2430
|
+
const words = kebab.split("-");
|
|
2431
|
+
return words.map((w, i) => i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w).join(" ");
|
|
2432
|
+
}
|
|
2433
|
+
function sanitizeForJsComment(s) {
|
|
2434
|
+
return s.replace(/\*\//g, "*\\/").replace(/[\r\n]+/g, " ").replace(/\s{2,}/g, " ").trim();
|
|
2435
|
+
}
|
|
2436
|
+
function lastSegment(packageName) {
|
|
2437
|
+
return packageName.replace(/^@[^/]+\//, "");
|
|
2438
|
+
}
|
|
2439
|
+
function pluginNameFromPackage(packageName) {
|
|
2440
|
+
const segment = lastSegment(packageName);
|
|
2441
|
+
if (segment.startsWith("ampless-plugin-")) {
|
|
2442
|
+
return segment.slice("ampless-plugin-".length);
|
|
2443
|
+
}
|
|
2444
|
+
if (segment.startsWith("plugin-")) {
|
|
2445
|
+
return segment.slice("plugin-".length);
|
|
2446
|
+
}
|
|
2447
|
+
return segment;
|
|
2448
|
+
}
|
|
2449
|
+
async function substituteFile2(filePath, vars) {
|
|
2450
|
+
const ext = extname3(filePath) || (basename3(filePath) === ".gitignore" ? ".gitignore" : "");
|
|
2451
|
+
if (!TEXT_EXTENSIONS3.has(ext)) return;
|
|
2452
|
+
const content = await readFile5(filePath, "utf-8");
|
|
2453
|
+
const replaced = content.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? `{{${key}}}`);
|
|
2454
|
+
if (replaced !== content) await writeFile5(filePath, replaced, "utf-8");
|
|
2455
|
+
}
|
|
2456
|
+
async function substituteDir2(dir, vars) {
|
|
2457
|
+
const entries = await readdir5(dir, { withFileTypes: true });
|
|
2458
|
+
await Promise.all(
|
|
2459
|
+
entries.map(async (entry) => {
|
|
2460
|
+
const fullPath = join5(dir, entry.name);
|
|
2461
|
+
if (entry.isDirectory()) {
|
|
2462
|
+
await substituteDir2(fullPath, vars);
|
|
2463
|
+
} else {
|
|
2464
|
+
await substituteFile2(fullPath, vars);
|
|
2465
|
+
}
|
|
2466
|
+
})
|
|
2467
|
+
);
|
|
2468
|
+
}
|
|
2469
|
+
var KEBAB_RE = /^[a-z][a-z0-9]*(?:-[a-z][a-z0-9]*)*$/;
|
|
2470
|
+
var PACKAGE_NAME_RE = /^(@[a-z][a-z0-9-]*\/)?[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
2471
|
+
async function runCreatePluginIn(destDir, args) {
|
|
2472
|
+
const mode = args.pluginMode ?? "local";
|
|
2473
|
+
let rawName = args.pluginName;
|
|
2474
|
+
if (!rawName) {
|
|
2475
|
+
if (args.skipConfirm === true) {
|
|
2476
|
+
throw new Error(
|
|
2477
|
+
mode === "local" ? "Plugin name is required when --skip-confirm is set. Pass it as the positional argument: `create-ampless plugin <name> --skip-confirm`." : "Plugin package name is required when --skip-confirm is set. Pass it as the positional argument: `create-ampless plugin <package-name> --standalone --skip-confirm`."
|
|
2478
|
+
);
|
|
2479
|
+
}
|
|
2480
|
+
const answer = await text3({
|
|
2481
|
+
message: mode === "local" ? 'Plugin name (kebab-case, e.g. "site-verification"):' : 'Plugin package name (e.g. "@scope/ampless-plugin-foo" or "ampless-plugin-foo"):',
|
|
2482
|
+
validate(v) {
|
|
2483
|
+
if (!v) return "Plugin name is required";
|
|
2484
|
+
const re = mode === "local" ? KEBAB_RE : PACKAGE_NAME_RE;
|
|
2485
|
+
if (!re.test(v)) {
|
|
2486
|
+
return mode === "local" ? 'Must be kebab-case starting with a lowercase letter (e.g. "site-verification")' : 'Must be a valid npm package name (e.g. "ampless-plugin-foo" or "@scope/ampless-plugin-foo")';
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
});
|
|
2490
|
+
if (isCancel3(answer)) {
|
|
2491
|
+
cancel3("Cancelled.");
|
|
2492
|
+
process.exit(0);
|
|
2493
|
+
}
|
|
2494
|
+
rawName = answer;
|
|
2495
|
+
} else {
|
|
2496
|
+
const re = mode === "local" ? KEBAB_RE : PACKAGE_NAME_RE;
|
|
2497
|
+
if (!re.test(rawName)) {
|
|
2498
|
+
throw new Error(
|
|
2499
|
+
mode === "local" ? `Invalid plugin name "${rawName}". Must be kebab-case (e.g. "site-verification").` : `Invalid plugin name "${rawName}". Must be a valid npm package name (e.g. "ampless-plugin-foo" or "@scope/ampless-plugin-foo").`
|
|
2500
|
+
);
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
const skipConfirm = args.skipConfirm === true;
|
|
2504
|
+
let trustLevel = args.pluginTrustLevel;
|
|
2505
|
+
if (!trustLevel) {
|
|
2506
|
+
if (skipConfirm) {
|
|
2507
|
+
trustLevel = "untrusted";
|
|
2508
|
+
} else {
|
|
2509
|
+
const answer = await select2({
|
|
2510
|
+
message: "Trust level:",
|
|
2511
|
+
options: VALID_PLUGIN_TRUST_LEVELS.map((t) => ({
|
|
2512
|
+
value: t,
|
|
2513
|
+
label: t,
|
|
2514
|
+
hint: t === "untrusted" ? "No special permissions (default)" : t === "trusted" ? "Can read site config" : "Full access to internal APIs"
|
|
2515
|
+
}))
|
|
2516
|
+
});
|
|
2517
|
+
if (isCancel3(answer)) {
|
|
2518
|
+
cancel3("Cancelled.");
|
|
2519
|
+
process.exit(0);
|
|
2520
|
+
}
|
|
2521
|
+
trustLevel = answer;
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
let capabilities = args.pluginCapabilities;
|
|
2525
|
+
if (!capabilities) {
|
|
2526
|
+
if (skipConfirm) {
|
|
2527
|
+
capabilities = ["publicHead", "adminSettings"];
|
|
2528
|
+
} else {
|
|
2529
|
+
const answer = await multiselect2({
|
|
2530
|
+
message: "Capabilities (space to toggle, enter to confirm):",
|
|
2531
|
+
options: VALID_PLUGIN_CAPABILITIES.map((c) => ({
|
|
2532
|
+
value: c,
|
|
2533
|
+
label: c
|
|
2534
|
+
})),
|
|
2535
|
+
initialValues: ["publicHead", "adminSettings"],
|
|
2536
|
+
required: false
|
|
2537
|
+
});
|
|
2538
|
+
if (isCancel3(answer)) {
|
|
2539
|
+
cancel3("Cancelled.");
|
|
2540
|
+
process.exit(0);
|
|
2541
|
+
}
|
|
2542
|
+
capabilities = answer;
|
|
2543
|
+
}
|
|
2544
|
+
}
|
|
2545
|
+
const rawDescription = args.pluginDescription ?? "";
|
|
2546
|
+
const description = sanitizeForJsComment(rawDescription);
|
|
2547
|
+
const descriptionJson = JSON.stringify(rawDescription);
|
|
2548
|
+
const nameKebab = mode === "local" ? rawName : pluginNameFromPackage(rawName);
|
|
2549
|
+
const packageName = mode === "standalone" ? rawName : nameKebab;
|
|
2550
|
+
if (!KEBAB_RE.test(nameKebab)) {
|
|
2551
|
+
throw new Error(
|
|
2552
|
+
`Invalid plugin name: "${nameKebab}" (derived from "${rawName}" by stripping the npm scope and the conventional ampless-plugin- prefix) is not valid kebab-case. Each segment must start with a letter and contain only letters and digits. Examples: "site-verification", "image-zoom".`
|
|
2553
|
+
);
|
|
2554
|
+
}
|
|
2555
|
+
const nameCamelCase = toCamelCase(nameKebab);
|
|
2556
|
+
const NamePascalCase = toPascalCase(nameKebab);
|
|
2557
|
+
const DisplayName = toDisplayName(nameKebab);
|
|
2558
|
+
void toKebab;
|
|
2559
|
+
let outputDir;
|
|
2560
|
+
if (mode === "local") {
|
|
2561
|
+
const problem = validateMountableProject(destDir);
|
|
2562
|
+
if (problem) {
|
|
2563
|
+
throw new Error(problem);
|
|
2564
|
+
}
|
|
2565
|
+
outputDir = join5(destDir, "plugins", nameKebab);
|
|
2566
|
+
if (existsSync7(outputDir)) {
|
|
2567
|
+
throw new Error(
|
|
2568
|
+
`Plugin directory already exists: plugins/${nameKebab}/
|
|
2569
|
+
Remove it first or choose a different name.`
|
|
2570
|
+
);
|
|
2571
|
+
}
|
|
2572
|
+
} else {
|
|
2573
|
+
const nameSegment = lastSegment(rawName);
|
|
2574
|
+
outputDir = join5(destDir, nameSegment);
|
|
2575
|
+
if (existsSync7(outputDir)) {
|
|
2576
|
+
throw new Error(
|
|
2577
|
+
`Directory already exists: ${nameSegment}/
|
|
2578
|
+
Remove it first or choose a different name.`
|
|
2579
|
+
);
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
const templateDir = pluginTemplateDir(mode);
|
|
2583
|
+
if (!existsSync7(templateDir)) {
|
|
2584
|
+
throw new Error(
|
|
2585
|
+
`Plugin template not found: ${templateDir}
|
|
2586
|
+
The ${mode === "local" ? "plugin-local" : "plugin-standalone"} template directory has not been added to the repo yet. Run \`git pull\` to pick up the latest templates.`
|
|
2587
|
+
);
|
|
2588
|
+
}
|
|
2589
|
+
await mkdir3(outputDir, { recursive: true });
|
|
2590
|
+
await cp4(templateDir, outputDir, { recursive: true });
|
|
2591
|
+
const capabilitiesList = capabilities && capabilities.length > 0 ? capabilities.map((c) => `'${c}'`).join(", ") : "";
|
|
2592
|
+
const capabilitiesJsonArray = capabilities && capabilities.length > 0 ? capabilities.map((c) => `"${c}"`).join(", ") : "";
|
|
2593
|
+
const vars = {
|
|
2594
|
+
// Use camelCase keys so they survive substituteVars' `\w+` regex.
|
|
2595
|
+
// Templates reference them as `{{nameKebab}}` (not `{{name-kebab}}`).
|
|
2596
|
+
nameKebab,
|
|
2597
|
+
nameCamelCase,
|
|
2598
|
+
NameCamelCase: NamePascalCase,
|
|
2599
|
+
packageName,
|
|
2600
|
+
description,
|
|
2601
|
+
descriptionJson,
|
|
2602
|
+
trustLevel: trustLevel ?? "untrusted",
|
|
2603
|
+
capabilitiesList,
|
|
2604
|
+
capabilitiesJsonArray,
|
|
2605
|
+
DisplayName,
|
|
2606
|
+
displayNameJa: DisplayName,
|
|
2607
|
+
// same as English for now; maintainers can localise
|
|
2608
|
+
amplessVersion: SCAFFOLD_AMPLESS_VERSION
|
|
2609
|
+
};
|
|
2610
|
+
await substituteDir2(outputDir, vars);
|
|
2611
|
+
return {
|
|
2612
|
+
mode,
|
|
2613
|
+
outputDir,
|
|
2614
|
+
pluginName: nameKebab,
|
|
2615
|
+
packageName: mode === "standalone" ? packageName : void 0
|
|
2616
|
+
};
|
|
2617
|
+
}
|
|
2618
|
+
async function runCreatePlugin(args) {
|
|
2619
|
+
const destDir = process.cwd();
|
|
2620
|
+
try {
|
|
2621
|
+
const result = await runCreatePluginIn(destDir, args);
|
|
2622
|
+
if (result.mode === "local") {
|
|
2623
|
+
const rel = `plugins/${result.pluginName}/`;
|
|
2624
|
+
const factoryName = `${toCamelCase(result.pluginName)}Plugin`;
|
|
2625
|
+
outro3(
|
|
2626
|
+
`${pc5.green("\u2714")} Plugin scaffolded at ${pc5.bold(rel)}
|
|
2627
|
+
|
|
2628
|
+
Register it in ${pc5.cyan("cms.config.ts")}:
|
|
2629
|
+
|
|
2630
|
+
import ${factoryName} from './plugins/${result.pluginName}'
|
|
2631
|
+
|
|
2632
|
+
export default defineConfig({
|
|
2633
|
+
// ...
|
|
2634
|
+
plugins: [
|
|
2635
|
+
${factoryName}(),
|
|
2636
|
+
],
|
|
2637
|
+
})
|
|
2638
|
+
|
|
2639
|
+
Then restart the dev server to pick up the new plugin.`
|
|
2640
|
+
);
|
|
2641
|
+
} else {
|
|
2642
|
+
const dirName = result.packageName ? lastSegment(result.packageName) : result.pluginName;
|
|
2643
|
+
outro3(
|
|
2644
|
+
`${pc5.green("\u2714")} Standalone plugin scaffolded at ${pc5.bold(dirName + "/")}
|
|
2645
|
+
|
|
2646
|
+
Next steps:
|
|
2647
|
+
${pc5.cyan(`cd ${dirName}`)}
|
|
2648
|
+
${pc5.cyan("pnpm install")}
|
|
2649
|
+
${pc5.cyan("pnpm test")}
|
|
2650
|
+
${pc5.cyan("pnpm build")}
|
|
2651
|
+
${pc5.cyan("pnpm publish --access public --tag alpha")}
|
|
2652
|
+
|
|
2653
|
+
Then add the published package to your ampless site:
|
|
2654
|
+
${pc5.cyan(`pnpm add ${result.packageName ?? result.pluginName}`)}`
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
} catch (err) {
|
|
2658
|
+
log5.error(err instanceof Error ? err.message : String(err));
|
|
2659
|
+
process.exit(1);
|
|
2660
|
+
}
|
|
2661
|
+
}
|
|
2662
|
+
|
|
2663
|
+
// src/setup-encryption-key.ts
|
|
2664
|
+
import { writeFile as writeFile6, mkdir as mkdir4, readFile as readFile6 } from "fs/promises";
|
|
2665
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2666
|
+
import { join as join6 } from "path";
|
|
2667
|
+
import { randomBytes } from "crypto";
|
|
2668
|
+
import { log as log6, confirm as confirm3, outro as outro4, cancel as cancel4 } from "@clack/prompts";
|
|
2669
|
+
import pc6 from "picocolors";
|
|
2670
|
+
var SECRETS_DIR = "amplify/secrets";
|
|
2671
|
+
var KEY_FILE = "encryption-key.ts";
|
|
2672
|
+
var KEY_FILE_PATH = `${SECRETS_DIR}/${KEY_FILE}`;
|
|
2673
|
+
function buildFileContent(keyB64) {
|
|
2674
|
+
return `// Generated by \`npx create-ampless setup-encryption-key\`.
|
|
2675
|
+
// AES-256-GCM key (32 bytes, base64) for plugin secret storage.
|
|
2676
|
+
// Rotate via the same CLI command. Existing ciphertext becomes unreadable on rotation.
|
|
2677
|
+
|
|
2678
|
+
export const PLUGIN_SECRET_ENCRYPTION_KEY = '${keyB64}'
|
|
2679
|
+
`;
|
|
2680
|
+
}
|
|
2681
|
+
function extractKeyValue(source) {
|
|
2682
|
+
const match = source.match(
|
|
2683
|
+
/\bPLUGIN_SECRET_ENCRYPTION_KEY\b\s*(?::\s*[^=]+)?=\s*(['"])(.*?)\1/
|
|
2684
|
+
);
|
|
2685
|
+
return match?.[2] ?? null;
|
|
2686
|
+
}
|
|
2687
|
+
function isEmptyPlaceholder(source) {
|
|
2688
|
+
return extractKeyValue(source) === "";
|
|
2689
|
+
}
|
|
2690
|
+
async function appendGitignoreEntry(cwd, entry) {
|
|
2691
|
+
const gitignorePath = join6(cwd, ".gitignore");
|
|
2692
|
+
if (existsSync8(gitignorePath)) {
|
|
2693
|
+
const content = await readFile6(gitignorePath, "utf-8");
|
|
2694
|
+
if (content.includes(entry)) return;
|
|
2695
|
+
await writeFile6(gitignorePath, content + (content.endsWith("\n") ? "" : "\n") + entry + "\n");
|
|
2696
|
+
} else {
|
|
2697
|
+
await writeFile6(gitignorePath, entry + "\n");
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
async function runSetupEncryptionKey(args) {
|
|
2701
|
+
const cwd = process.cwd();
|
|
2702
|
+
if (!existsSync8(join6(cwd, "amplify"))) {
|
|
2703
|
+
log6.error(
|
|
2704
|
+
`No \`amplify/\` directory found in ${cwd}.
|
|
2705
|
+
Run this command from the root of an ampless project (where amplify/backend.ts lives).`
|
|
2706
|
+
);
|
|
2707
|
+
process.exit(1);
|
|
2708
|
+
}
|
|
2709
|
+
const keyFilePath = join6(cwd, KEY_FILE_PATH);
|
|
2710
|
+
if (existsSync8(keyFilePath)) {
|
|
2711
|
+
const existingContent = await readFile6(keyFilePath, "utf-8");
|
|
2712
|
+
if (!isEmptyPlaceholder(existingContent)) {
|
|
2713
|
+
log6.warn(
|
|
2714
|
+
pc6.yellow(
|
|
2715
|
+
`${KEY_FILE_PATH} already exists.
|
|
2716
|
+
Overwriting it will make all existing plugin secrets unreadable
|
|
2717
|
+
until each secret is re-saved through the admin UI.`
|
|
2718
|
+
)
|
|
2719
|
+
);
|
|
2720
|
+
const confirmed = await confirm3({
|
|
2721
|
+
message: "Overwrite the existing encryption key?",
|
|
2722
|
+
initialValue: false
|
|
2723
|
+
});
|
|
2724
|
+
if (confirmed === false || confirmed === void 0) {
|
|
2725
|
+
cancel4("Cancelled \u2014 existing key preserved.");
|
|
2726
|
+
return;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
const keyBytes = randomBytes(32);
|
|
2731
|
+
const keyB64 = keyBytes.toString("base64");
|
|
2732
|
+
const secretsDir = join6(cwd, SECRETS_DIR);
|
|
2733
|
+
if (!existsSync8(secretsDir)) {
|
|
2734
|
+
await mkdir4(secretsDir, { recursive: true });
|
|
2735
|
+
}
|
|
2736
|
+
try {
|
|
2737
|
+
await writeFile6(keyFilePath, buildFileContent(keyB64), "utf-8");
|
|
2738
|
+
} catch (err) {
|
|
2739
|
+
log6.error(
|
|
2740
|
+
`Failed to write ${KEY_FILE_PATH}: ${err instanceof Error ? err.message : String(err)}`
|
|
2741
|
+
);
|
|
2742
|
+
process.exit(1);
|
|
2743
|
+
}
|
|
2744
|
+
if (args.gitignore) {
|
|
2745
|
+
try {
|
|
2746
|
+
await appendGitignoreEntry(cwd, KEY_FILE_PATH);
|
|
2747
|
+
log6.info(`Added ${KEY_FILE_PATH} to .gitignore`);
|
|
2748
|
+
} catch (err) {
|
|
2749
|
+
log6.warn(
|
|
2750
|
+
`Could not update .gitignore: ${err instanceof Error ? err.message : String(err)}
|
|
2751
|
+
Add \`${KEY_FILE_PATH}\` to your .gitignore manually.`
|
|
2752
|
+
);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
outro4(
|
|
2756
|
+
`${pc6.green("\u2714")} Generated ${pc6.cyan(KEY_FILE_PATH)}
|
|
2757
|
+
|
|
2758
|
+
${pc6.bold("Next steps:")}
|
|
2759
|
+
1. In \`amplify/backend.ts\`, add:
|
|
2760
|
+
import { PLUGIN_SECRET_ENCRYPTION_KEY } from './secrets/encryption-key.js'
|
|
2761
|
+
defineAmplessBackend({ ..., pluginSecretEncryptionKey: PLUGIN_SECRET_ENCRYPTION_KEY })
|
|
2762
|
+
2. Redeploy (or restart the sandbox) to inject the key into Lambda env vars.
|
|
2763
|
+
3. Save plugin secrets via the admin UI \u2014 they are now encrypted at rest.
|
|
2764
|
+
|
|
2765
|
+
${pc6.bold("Threat model summary:")}
|
|
2766
|
+
${pc6.green("\u2713")} AWS Console operator browsing DDB sees ciphertext only
|
|
2767
|
+
${pc6.yellow("\u26A0")} Anyone with source repo / deploy artifact access can decrypt
|
|
2768
|
+
${pc6.red("\u2717")} Malicious trusted plugin in the same Lambda can also decrypt
|
|
2769
|
+
(per-plugin Lambda isolation = roadmap)
|
|
2770
|
+
|
|
2771
|
+
` + (args.gitignore ? ` ${pc6.dim(`${KEY_FILE_PATH} added to .gitignore.`)}
|
|
2772
|
+
|
|
2773
|
+
` : ` ${pc6.yellow("Tip:")} If committing to a PUBLIC repo, run with --gitignore instead
|
|
2774
|
+
and distribute the key separately.
|
|
2775
|
+
|
|
2776
|
+
`) + ` ${pc6.yellow("Rotation:")} Re-run this command and confirm overwrite.
|
|
2777
|
+
Existing ciphertext will be unreadable; re-save via /admin/plugins.`
|
|
2778
|
+
);
|
|
2779
|
+
}
|
|
2780
|
+
|
|
2781
|
+
// src/index.ts
|
|
2782
|
+
import pc7 from "picocolors";
|
|
2131
2783
|
var DEFAULT_THEMES = VALID_THEMES;
|
|
2132
2784
|
function buildNonInteractiveOpts(args) {
|
|
2133
2785
|
const projectName = args.projectName ?? (() => {
|
|
2134
|
-
const b =
|
|
2786
|
+
const b = basename4(process.cwd());
|
|
2135
2787
|
return b && b !== "/" ? b : "my-ampless-site";
|
|
2136
2788
|
})();
|
|
2137
2789
|
const siteName = args.siteName ?? "My Blog";
|
|
@@ -2152,18 +2804,18 @@ function warnIgnoredScaffoldFlags(args) {
|
|
|
2152
2804
|
if (args.plugins) ignored.push("--plugins");
|
|
2153
2805
|
if (args.projectName) ignored.push("<project-name> positional");
|
|
2154
2806
|
if (ignored.length > 0) {
|
|
2155
|
-
|
|
2807
|
+
log7.warn(`--mount mode ignores: ${ignored.join(", ")}`);
|
|
2156
2808
|
}
|
|
2157
2809
|
}
|
|
2158
2810
|
async function runMount(args) {
|
|
2159
2811
|
const destDir = process.cwd();
|
|
2160
|
-
const projectName =
|
|
2812
|
+
const projectName = basename4(destDir);
|
|
2161
2813
|
warnIgnoredScaffoldFlags(args);
|
|
2162
2814
|
const problem = validateMountableProject(destDir);
|
|
2163
2815
|
if (problem) {
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
"Run `npx create-ampless@
|
|
2816
|
+
log7.error(problem);
|
|
2817
|
+
log7.info(
|
|
2818
|
+
"Run `npx create-ampless@latest <name>` first to scaffold, or `cd` into a scaffolded project before passing --mount."
|
|
2167
2819
|
);
|
|
2168
2820
|
process.exit(1);
|
|
2169
2821
|
}
|
|
@@ -2176,23 +2828,23 @@ async function runMount(args) {
|
|
|
2176
2828
|
if (err instanceof PreflightError) {
|
|
2177
2829
|
process.exit(1);
|
|
2178
2830
|
}
|
|
2179
|
-
|
|
2831
|
+
log7.error(err instanceof Error ? err.message : String(err));
|
|
2180
2832
|
process.exit(1);
|
|
2181
2833
|
}
|
|
2182
2834
|
}
|
|
2183
2835
|
function printDeployResult(result) {
|
|
2184
2836
|
const lines = [
|
|
2185
|
-
`${
|
|
2837
|
+
`${pc7.green("\u2714")} Project deployed`,
|
|
2186
2838
|
``,
|
|
2187
|
-
` GitHub: ${
|
|
2188
|
-
` Amplify app: ${
|
|
2189
|
-
` Amplify URL: ${
|
|
2839
|
+
` GitHub: ${pc7.cyan(result.githubRepoUrl)}`,
|
|
2840
|
+
` Amplify app: ${pc7.cyan(result.amplifyAppId)}`,
|
|
2841
|
+
` Amplify URL: ${pc7.cyan(result.amplifyAppUrl)}`
|
|
2190
2842
|
];
|
|
2191
2843
|
if (result.domainUrl) {
|
|
2192
|
-
lines.push(` Custom domain: ${
|
|
2844
|
+
lines.push(` Custom domain: ${pc7.cyan(result.domainUrl)}`);
|
|
2193
2845
|
}
|
|
2194
2846
|
if (result.domainVerification && result.domainVerification.length > 0) {
|
|
2195
|
-
lines.push("", ` ${
|
|
2847
|
+
lines.push("", ` ${pc7.bold("Add these DNS records to verify the domain:")}`);
|
|
2196
2848
|
for (const v of result.domainVerification) {
|
|
2197
2849
|
lines.push(` ${v.cname} CNAME ${v.target}`);
|
|
2198
2850
|
}
|
|
@@ -2200,9 +2852,9 @@ function printDeployResult(result) {
|
|
|
2200
2852
|
lines.push(
|
|
2201
2853
|
"",
|
|
2202
2854
|
` First build is now running in Amplify Hosting.`,
|
|
2203
|
-
` Watch it at ${
|
|
2855
|
+
` Watch it at ${pc7.cyan(`https://console.aws.amazon.com/amplify/home#/${result.amplifyAppId}`)}`
|
|
2204
2856
|
);
|
|
2205
|
-
|
|
2857
|
+
outro5(lines.join("\n"));
|
|
2206
2858
|
}
|
|
2207
2859
|
async function main() {
|
|
2208
2860
|
const args = parseDeployArgs(process.argv.slice(2));
|
|
@@ -2211,7 +2863,7 @@ async function main() {
|
|
|
2211
2863
|
return;
|
|
2212
2864
|
}
|
|
2213
2865
|
for (const flag of args.unknown) {
|
|
2214
|
-
|
|
2866
|
+
log7.warn(`Unknown argument ignored: ${flag}`);
|
|
2215
2867
|
}
|
|
2216
2868
|
if (args.upgrade) {
|
|
2217
2869
|
await runUpgrade(args);
|
|
@@ -2221,6 +2873,14 @@ async function main() {
|
|
|
2221
2873
|
await runCopyTheme(args);
|
|
2222
2874
|
return;
|
|
2223
2875
|
}
|
|
2876
|
+
if (args.createPlugin) {
|
|
2877
|
+
await runCreatePlugin(args);
|
|
2878
|
+
return;
|
|
2879
|
+
}
|
|
2880
|
+
if (args.setupEncryptionKey) {
|
|
2881
|
+
await runSetupEncryptionKey(args);
|
|
2882
|
+
return;
|
|
2883
|
+
}
|
|
2224
2884
|
if (args.mount) {
|
|
2225
2885
|
await runMount(args);
|
|
2226
2886
|
return;
|
|
@@ -2233,8 +2893,8 @@ async function main() {
|
|
|
2233
2893
|
}
|
|
2234
2894
|
if (!opts) return;
|
|
2235
2895
|
const destDir = resolve5(process.cwd(), opts.projectName);
|
|
2236
|
-
if (
|
|
2237
|
-
|
|
2896
|
+
if (existsSync9(destDir)) {
|
|
2897
|
+
log7.error(`Directory already exists: ${destDir}`);
|
|
2238
2898
|
process.exit(1);
|
|
2239
2899
|
}
|
|
2240
2900
|
const sharedDir = sharedTemplateDir();
|
|
@@ -2245,7 +2905,7 @@ async function main() {
|
|
|
2245
2905
|
s.stop("Done!");
|
|
2246
2906
|
} catch (err) {
|
|
2247
2907
|
s.stop("Failed.");
|
|
2248
|
-
|
|
2908
|
+
log7.error(String(err));
|
|
2249
2909
|
process.exit(1);
|
|
2250
2910
|
}
|
|
2251
2911
|
if (args.deploy) {
|
|
@@ -2258,19 +2918,18 @@ async function main() {
|
|
|
2258
2918
|
if (err instanceof PreflightError) {
|
|
2259
2919
|
process.exit(1);
|
|
2260
2920
|
}
|
|
2261
|
-
|
|
2921
|
+
log7.error(err instanceof Error ? err.message : String(err));
|
|
2262
2922
|
process.exit(1);
|
|
2263
2923
|
}
|
|
2264
2924
|
return;
|
|
2265
2925
|
}
|
|
2266
|
-
|
|
2267
|
-
`${
|
|
2926
|
+
outro5(
|
|
2927
|
+
`${pc7.green("\u2714")} Project created at ${pc7.bold(opts.projectName)}
|
|
2268
2928
|
|
|
2269
2929
|
Next steps:
|
|
2270
|
-
${
|
|
2271
|
-
${
|
|
2272
|
-
${
|
|
2273
|
-
${pc5.cyan("npm run dev")} ${pc5.dim("# start Next.js")}`
|
|
2930
|
+
${pc7.cyan("cd")} ${opts.projectName}
|
|
2931
|
+
${pc7.cyan("npm install")}
|
|
2932
|
+
${pc7.cyan("npm run sandbox")} ${pc7.dim("# deploy sandbox + start Next.js")}`
|
|
2274
2933
|
);
|
|
2275
2934
|
}
|
|
2276
2935
|
main().catch((err) => {
|