create-ampless 0.2.0-alpha.6 → 0.2.0-alpha.8

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/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { spinner as spinner2, outro, log as log3 } from "@clack/prompts";
5
- import { existsSync as existsSync2 } from "fs";
6
- import { basename as basename3, resolve as resolve4 } from "path";
5
+ import { existsSync as existsSync4 } from "fs";
6
+ import { basename as basename3, resolve as resolve5 } from "path";
7
7
 
8
8
  // src/prompts.ts
9
9
  import * as p from "@clack/prompts";
@@ -208,6 +208,7 @@ var STRING_FLAGS = /* @__PURE__ */ new Set([
208
208
  ]);
209
209
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
210
210
  "--deploy",
211
+ "--mount",
211
212
  "--github-private",
212
213
  "--create-iam-role",
213
214
  "--skip-confirm",
@@ -217,6 +218,7 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
217
218
  function parseDeployArgs(argv) {
218
219
  const out = {
219
220
  deploy: false,
221
+ mount: false,
220
222
  githubPrivate: false,
221
223
  createIamRole: false,
222
224
  skipConfirm: false,
@@ -237,6 +239,9 @@ function parseDeployArgs(argv) {
237
239
  case "--deploy":
238
240
  out.deploy = true;
239
241
  break;
242
+ case "--mount":
243
+ out.mount = true;
244
+ break;
240
245
  case "--github-private":
241
246
  out.githubPrivate = true;
242
247
  break;
@@ -324,6 +329,7 @@ var HELP_TEXT = `create-ampless \u2014 scaffold an ampless project
324
329
 
325
330
  Usage:
326
331
  npx create-ampless@alpha <project-name> [options]
332
+ npx create-ampless@alpha --mount [options] # in an existing project dir
327
333
 
328
334
  Options:
329
335
  --site-name <name> Site display name (default: "My Blog")
@@ -335,6 +341,12 @@ Options:
335
341
  (default: seo)
336
342
  --deploy Also create GitHub repo + Amplify Hosting app and
337
343
  kick off the first deploy after scaffolding
344
+ --mount Skip scaffolding and mount the CURRENT directory
345
+ onto a new GitHub repo + Amplify Hosting app.
346
+ Use after you've scaffolded and tested locally
347
+ with 'npx ampx sandbox' and now want to publish.
348
+ Implies --deploy. Scaffold flags (--site-name,
349
+ --themes, --plugins) are ignored.
338
350
  --github-owner <login> GitHub owner (user or org). Defaults to the
339
351
  authenticated 'gh' user
340
352
  --github-private Create a private repo (default: public)
@@ -361,8 +373,9 @@ import { execa as execa3 } from "execa";
361
373
 
362
374
  // src/deploy.ts
363
375
  import { execa as execa2 } from "execa";
376
+ import { existsSync as existsSync3 } from "fs";
364
377
  import { writeFile as writeFile2 } from "fs/promises";
365
- import { basename as basename2, resolve as resolve3 } from "path";
378
+ import { basename as basename2, resolve as resolve4 } from "path";
366
379
  import { spinner, log } from "@clack/prompts";
367
380
  import pc2 from "picocolors";
368
381
 
@@ -787,7 +800,7 @@ async function runPreflight(opts, extra = {}) {
787
800
  if (hasGh) await checkGhAuth(problems);
788
801
  const hasAws = await checkAwsInstalled(problems);
789
802
  const identity = hasAws ? await checkAwsCreds(opts, problems) : void 0;
790
- if (hasGh && opts.githubOwner) {
803
+ if (hasGh && opts.githubOwner && !extra.mountMode) {
791
804
  await checkGithubRepoFree(opts, opts.githubOwner, problems);
792
805
  }
793
806
  if (identity) {
@@ -838,6 +851,55 @@ function printPreflightReport(problems) {
838
851
  `);
839
852
  }
840
853
 
854
+ // src/mount.ts
855
+ import { existsSync as existsSync2 } from "fs";
856
+ import { resolve as resolve3 } from "path";
857
+ function validateMountableProject(destDir) {
858
+ const required = ["package.json", "cms.config.ts"];
859
+ for (const f of required) {
860
+ if (!existsSync2(resolve3(destDir, f))) {
861
+ return `Not an ampless project (missing ${f} in ${destDir})`;
862
+ }
863
+ }
864
+ const amplifyFiles = ["amplify/backend.ts", "amplify/data/resource.ts"];
865
+ if (!amplifyFiles.some((f) => existsSync2(resolve3(destDir, f)))) {
866
+ return `Not an ampless project (missing amplify/ in ${destDir})`;
867
+ }
868
+ return null;
869
+ }
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
+ function originPointsAt(origin, owner, name) {
892
+ const candidates = [
893
+ `https://github.com/${owner}/${name}`,
894
+ `https://github.com/${owner}/${name}.git`,
895
+ `git@github.com:${owner}/${name}`,
896
+ `git@github.com:${owner}/${name}.git`,
897
+ `ssh://git@github.com/${owner}/${name}`,
898
+ `ssh://git@github.com/${owner}/${name}.git`
899
+ ];
900
+ return candidates.includes(origin);
901
+ }
902
+
841
903
  // src/deploy.ts
842
904
  var AMPLIFY_BUILD_SPEC = `version: 1
843
905
  frontend:
@@ -945,27 +1007,115 @@ function awsArgs(opts, extra) {
945
1007
  base.push("--output", "json");
946
1008
  return base;
947
1009
  }
948
- async function gitInitCommit(dir) {
949
- await run("git", ["init", "-b", "main"], { cwd: dir, step: "git init" });
950
- await run("git", ["add", "."], { cwd: dir, step: "git add" });
951
- await run(
952
- "git",
953
- ["commit", "-m", "Initial scaffold (create-ampless)"],
954
- { cwd: dir, step: "git commit" }
955
- );
1010
+ async function isGitRepo(dir) {
1011
+ const r = await execa2("git", ["rev-parse", "--git-dir"], { cwd: dir, reject: false });
1012
+ return r.exitCode === 0;
1013
+ }
1014
+ async function isDirty(dir) {
1015
+ const r = await execa2("git", ["status", "--porcelain"], { cwd: dir, reject: false });
1016
+ return (r.stdout?.toString() ?? "").trim().length > 0;
1017
+ }
1018
+ async function currentBranch(dir) {
1019
+ const r = await execa2("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
1020
+ cwd: dir,
1021
+ reject: false
1022
+ });
1023
+ if (r.exitCode !== 0) return null;
1024
+ const name = r.stdout?.toString().trim();
1025
+ return name && name !== "HEAD" ? name : null;
1026
+ }
1027
+ async function ensureGitignore(dir) {
1028
+ const target = resolve4(dir, ".gitignore");
1029
+ if (existsSync3(target)) return;
1030
+ await writeFile2(target, MOUNT_DEFAULT_GITIGNORE, "utf-8");
1031
+ }
1032
+ async function gitInitOrReuse(dir, mount) {
1033
+ const repo = await isGitRepo(dir);
1034
+ if (!repo) {
1035
+ await run("git", ["init", "-b", "main"], { cwd: dir, step: "git init" });
1036
+ }
1037
+ const dirty = await isDirty(dir);
1038
+ if (dirty) {
1039
+ await run("git", ["add", "."], { cwd: dir, step: "git add" });
1040
+ const message = mount && repo ? "Prepare for create-ampless --mount" : "Initial scaffold (create-ampless)";
1041
+ await run("git", ["commit", "-m", message], { cwd: dir, step: "git commit" });
1042
+ }
1043
+ if (mount) {
1044
+ const branch = await currentBranch(dir);
1045
+ if (branch && branch !== "main") {
1046
+ log.warn(
1047
+ `Current branch is "${branch}" \u2014 Amplify Hosting will be wired to the "main" branch. Push from "main" later, or rename your branch with \`git branch -m main\`.`
1048
+ );
1049
+ }
1050
+ }
956
1051
  }
957
1052
  function shortName2(opts) {
958
1053
  return basename2(opts.projectDir);
959
1054
  }
1055
+ async function ghRepoExists(name, token) {
1056
+ const r = await execa2("gh", ["repo", "view", name, "--json", "nameWithOwner"], {
1057
+ reject: false,
1058
+ env: { ...process.env, GH_TOKEN: token }
1059
+ });
1060
+ if (r.exitCode !== 0) return false;
1061
+ try {
1062
+ const parsed = JSON.parse(r.stdout?.toString() ?? "{}");
1063
+ const resolved = parsed.nameWithOwner?.toLowerCase();
1064
+ return resolved === name.toLowerCase();
1065
+ } catch {
1066
+ return true;
1067
+ }
1068
+ }
1069
+ async function getOriginUrl(dir) {
1070
+ const r = await execa2("git", ["remote", "get-url", "origin"], {
1071
+ cwd: dir,
1072
+ reject: false
1073
+ });
1074
+ if (r.exitCode !== 0) return null;
1075
+ const url = r.stdout?.toString().trim();
1076
+ return url ? url : null;
1077
+ }
1078
+ function repoHttpsUrl(owner, name) {
1079
+ return `https://github.com/${owner}/${name}`;
1080
+ }
960
1081
  async function ghRepoCreate(opts) {
1082
+ const owner = opts.githubOwner;
1083
+ const name = shortName2(opts);
1084
+ const fullName = `${owner}/${name}`;
961
1085
  const visibility = opts.githubPrivate ? "--private" : "--public";
962
- const name = `${opts.githubOwner}/${shortName2(opts)}`;
1086
+ const exists = await ghRepoExists(fullName, opts.githubToken);
1087
+ if (exists) {
1088
+ const origin = await getOriginUrl(opts.projectDir);
1089
+ if (origin && !originPointsAt(origin, owner, name)) {
1090
+ throw new Error(
1091
+ `Existing 'origin' remote points at ${origin}, but --mount expects ${fullName}.
1092
+ Remove or rename the existing remote (\`git remote remove origin\`) and re-run.`
1093
+ );
1094
+ }
1095
+ if (!origin) {
1096
+ await run(
1097
+ "git",
1098
+ ["remote", "add", "origin", `https://github.com/${fullName}.git`],
1099
+ { cwd: opts.projectDir, step: "git remote add origin" }
1100
+ );
1101
+ }
1102
+ await run(
1103
+ "git",
1104
+ ["push", "-u", "origin", "HEAD:main"],
1105
+ {
1106
+ cwd: opts.projectDir,
1107
+ step: "git push origin main",
1108
+ env: { ...process.env, GH_TOKEN: opts.githubToken }
1109
+ }
1110
+ );
1111
+ return repoHttpsUrl(owner, name);
1112
+ }
963
1113
  const out = await run(
964
1114
  "gh",
965
1115
  [
966
1116
  "repo",
967
1117
  "create",
968
- name,
1118
+ fullName,
969
1119
  "--source",
970
1120
  opts.projectDir,
971
1121
  "--push",
@@ -981,7 +1131,7 @@ async function ghRepoCreate(opts) {
981
1131
  );
982
1132
  const match = out.match(/https:\/\/github\.com\/[^\s]+/g);
983
1133
  if (match && match.length > 0) return match[match.length - 1].replace(/[.,]$/, "");
984
- return `https://github.com/${name}`;
1134
+ return repoHttpsUrl(owner, name);
985
1135
  }
986
1136
  async function amplifyCreateApp(opts, repoUrl, iamServiceRoleArn) {
987
1137
  const cmd = [
@@ -1188,7 +1338,8 @@ var PreflightError = class extends Error {
1188
1338
  async function runDeploy(opts) {
1189
1339
  const pre = await runPreflight(opts, {
1190
1340
  iamServiceRoleArn: opts.iamServiceRoleArn,
1191
- createIamRole: opts.createIamRole === true
1341
+ createIamRole: opts.createIamRole === true,
1342
+ mountMode: opts.mount === true
1192
1343
  });
1193
1344
  if (pre.problems.length > 0) {
1194
1345
  printPreflightReport(pre.problems);
@@ -1209,7 +1360,7 @@ async function runDeploy(opts) {
1209
1360
  );
1210
1361
  }
1211
1362
  }
1212
- await writeFile2(resolve3(opts.projectDir, "amplify.yml"), AMPLIFY_BUILD_SPEC, "utf-8");
1363
+ await writeFile2(resolve4(opts.projectDir, "amplify.yml"), AMPLIFY_BUILD_SPEC, "utf-8");
1213
1364
  const created = {};
1214
1365
  const fail = (step, cause) => {
1215
1366
  const msg = cause instanceof Error ? cause.message : String(cause);
@@ -1233,10 +1384,14 @@ Re-run after cleaning up.` : "";
1233
1384
  ${msg}${cleanupBlock}`);
1234
1385
  };
1235
1386
  let s = spinner();
1236
- s.start("git init + initial commit");
1387
+ const gitMsg = opts.mount ? "git: preparing repo for mount" : "git init + initial commit";
1388
+ s.start(gitMsg);
1237
1389
  try {
1238
- await gitInitCommit(opts.projectDir);
1239
- s.stop("git: committed initial scaffold");
1390
+ if (opts.mount) {
1391
+ await ensureGitignore(opts.projectDir);
1392
+ }
1393
+ await gitInitOrReuse(opts.projectDir, opts.mount === true);
1394
+ s.stop(opts.mount ? "git: ready" : "git: committed initial scaffold");
1240
1395
  } catch (err) {
1241
1396
  s.stop("git: failed");
1242
1397
  fail("git init / commit", err);
@@ -1342,7 +1497,7 @@ async function gatherDeployOptions(args, projectDir, projectName) {
1342
1497
  githubOwner = answer.trim();
1343
1498
  }
1344
1499
  let githubPrivate = args.githubPrivate;
1345
- if (!args.githubPrivate) {
1500
+ if (!args.githubPrivate && !args.skipConfirm) {
1346
1501
  const choice = await p2.select({
1347
1502
  message: "Repository visibility",
1348
1503
  options: [
@@ -1468,6 +1623,65 @@ function buildNonInteractiveOpts(args) {
1468
1623
  plugins
1469
1624
  };
1470
1625
  }
1626
+ function warnIgnoredScaffoldFlags(args) {
1627
+ const ignored = [];
1628
+ if (args.siteName) ignored.push("--site-name");
1629
+ if (args.themes) ignored.push("--themes");
1630
+ if (args.plugins) ignored.push("--plugins");
1631
+ if (args.projectName) ignored.push("<project-name> positional");
1632
+ if (ignored.length > 0) {
1633
+ log3.warn(`--mount mode ignores: ${ignored.join(", ")}`);
1634
+ }
1635
+ }
1636
+ async function runMount(args) {
1637
+ const destDir = process.cwd();
1638
+ const projectName = basename3(destDir);
1639
+ warnIgnoredScaffoldFlags(args);
1640
+ const problem = validateMountableProject(destDir);
1641
+ if (problem) {
1642
+ log3.error(problem);
1643
+ log3.info(
1644
+ "Run `npx create-ampless@alpha <name>` first to scaffold, or `cd` into a scaffolded project before passing --mount."
1645
+ );
1646
+ process.exit(1);
1647
+ }
1648
+ const deployOpts = await gatherDeployOptions(args, destDir, projectName);
1649
+ if (!deployOpts) return;
1650
+ try {
1651
+ const result = await runDeploy({ ...deployOpts, mount: true });
1652
+ printDeployResult(result);
1653
+ } catch (err) {
1654
+ if (err instanceof PreflightError) {
1655
+ process.exit(1);
1656
+ }
1657
+ log3.error(err instanceof Error ? err.message : String(err));
1658
+ process.exit(1);
1659
+ }
1660
+ }
1661
+ function printDeployResult(result) {
1662
+ const lines = [
1663
+ `${pc3.green("\u2714")} Project deployed`,
1664
+ ``,
1665
+ ` GitHub: ${pc3.cyan(result.githubRepoUrl)}`,
1666
+ ` Amplify app: ${pc3.cyan(result.amplifyAppId)}`,
1667
+ ` Amplify URL: ${pc3.cyan(result.amplifyAppUrl)}`
1668
+ ];
1669
+ if (result.domainUrl) {
1670
+ lines.push(` Custom domain: ${pc3.cyan(result.domainUrl)}`);
1671
+ }
1672
+ if (result.domainVerification && result.domainVerification.length > 0) {
1673
+ lines.push("", ` ${pc3.bold("Add these DNS records to verify the domain:")}`);
1674
+ for (const v of result.domainVerification) {
1675
+ lines.push(` ${v.cname} CNAME ${v.target}`);
1676
+ }
1677
+ }
1678
+ lines.push(
1679
+ "",
1680
+ ` First build is now running in Amplify Hosting.`,
1681
+ ` Watch it at ${pc3.cyan(`https://console.aws.amazon.com/amplify/home#/${result.amplifyAppId}`)}`
1682
+ );
1683
+ outro(lines.join("\n"));
1684
+ }
1471
1685
  async function main() {
1472
1686
  const args = parseDeployArgs(process.argv.slice(2));
1473
1687
  if (args.help) {
@@ -1477,6 +1691,10 @@ async function main() {
1477
1691
  for (const flag of args.unknown) {
1478
1692
  log3.warn(`Unknown argument ignored: ${flag}`);
1479
1693
  }
1694
+ if (args.mount) {
1695
+ await runMount(args);
1696
+ return;
1697
+ }
1480
1698
  let opts;
1481
1699
  if (args.skipConfirm) {
1482
1700
  opts = buildNonInteractiveOpts(args);
@@ -1484,8 +1702,8 @@ async function main() {
1484
1702
  opts = await runPrompts(args.projectName);
1485
1703
  }
1486
1704
  if (!opts) return;
1487
- const destDir = resolve4(process.cwd(), opts.projectName);
1488
- if (existsSync2(destDir)) {
1705
+ const destDir = resolve5(process.cwd(), opts.projectName);
1706
+ if (existsSync4(destDir)) {
1489
1707
  log3.error(`Directory already exists: ${destDir}`);
1490
1708
  process.exit(1);
1491
1709
  }
@@ -1505,28 +1723,7 @@ async function main() {
1505
1723
  if (!deployOpts) return;
1506
1724
  try {
1507
1725
  const result = await runDeploy(deployOpts);
1508
- const lines = [
1509
- `${pc3.green("\u2714")} Project deployed`,
1510
- ``,
1511
- ` GitHub: ${pc3.cyan(result.githubRepoUrl)}`,
1512
- ` Amplify app: ${pc3.cyan(result.amplifyAppId)}`,
1513
- ` Amplify URL: ${pc3.cyan(result.amplifyAppUrl)}`
1514
- ];
1515
- if (result.domainUrl) {
1516
- lines.push(` Custom domain: ${pc3.cyan(result.domainUrl)}`);
1517
- }
1518
- if (result.domainVerification && result.domainVerification.length > 0) {
1519
- lines.push("", ` ${pc3.bold("Add these DNS records to verify the domain:")}`);
1520
- for (const v of result.domainVerification) {
1521
- lines.push(` ${v.cname} CNAME ${v.target}`);
1522
- }
1523
- }
1524
- lines.push(
1525
- "",
1526
- ` First build is now running in Amplify Hosting.`,
1527
- ` Watch it at ${pc3.cyan(`https://console.aws.amazon.com/amplify/home#/${result.amplifyAppId}`)}`
1528
- );
1529
- outro(lines.join("\n"));
1726
+ printDeployResult(result);
1530
1727
  } catch (err) {
1531
1728
  if (err instanceof PreflightError) {
1532
1729
  process.exit(1);
@@ -24,9 +24,9 @@
24
24
  "@ampless/plugin-rss": "^0.2.0-alpha.1",
25
25
  "@ampless/plugin-seo": "^0.2.0-alpha.1",
26
26
  "@ampless/plugin-webhook": "^0.2.0-alpha.1",
27
- "@ampless/admin": "^0.2.0-alpha.5",
28
- "@ampless/backend": "^0.2.0-alpha.1",
29
- "@ampless/runtime": "^0.2.0-alpha.2",
27
+ "@ampless/admin": "^0.2.0-alpha.6",
28
+ "@ampless/backend": "^0.2.0-alpha.2",
29
+ "@ampless/runtime": "^0.2.0-alpha.3",
30
30
  "@digital-go-jp/tailwind-theme-plugin": "^0.3.4",
31
31
  "ampless": "^0.2.0-alpha.1",
32
32
  "aws-amplify": "^6.10.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ampless",
3
- "version": "0.2.0-alpha.6",
3
+ "version": "0.2.0-alpha.8",
4
4
  "description": "Create a new ampless project",
5
5
  "license": "MIT",
6
6
  "type": "module",