create-ovok 0.1.0-dev.22 → 0.1.0-dev.28

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.md CHANGED
@@ -24,6 +24,9 @@ Flags answer their questions, and `--yes` takes the recommended setup for the re
24
24
  Outside a terminal, such as in CI, nothing is asked: unanswered questions take the recommended
25
25
  setup.
26
26
 
27
+ In a terminal it also asks npm for the latest create-ovok and, when there is a newer one, ends
28
+ with the version to run next time.
29
+
27
30
  Inside the workspace:
28
31
 
29
32
  ```bash
@@ -0,0 +1,15 @@
1
+ const REGISTRY = "https://registry.npmjs.org";
2
+ /** The version npm installs by default, or null when the registry does not answer in time. */
3
+ export async function latestVersion(packageName, signal) {
4
+ const url = `${REGISTRY}/-/package/${encodeURIComponent(packageName)}/dist-tags`;
5
+ try {
6
+ const response = await fetch(url, { signal });
7
+ if (!response.ok)
8
+ return null;
9
+ const { latest } = (await response.json());
10
+ return typeof latest === "string" ? latest : null;
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
@@ -0,0 +1,20 @@
1
+ const NUMERIC_ORDER = new Intl.Collator("en", { numeric: true });
2
+ /** Semver order: numbers compare as numbers, and a release outranks its own prereleases. */
3
+ export function isNewer(candidate, current) {
4
+ const [candidateCore, candidatePrerelease] = splitPrerelease(candidate);
5
+ const [currentCore, currentPrerelease] = splitPrerelease(current);
6
+ const coreOrder = NUMERIC_ORDER.compare(candidateCore, currentCore);
7
+ if (coreOrder !== 0)
8
+ return coreOrder > 0;
9
+ if (candidatePrerelease === "" || currentPrerelease === "") {
10
+ return candidatePrerelease === "" && currentPrerelease !== "";
11
+ }
12
+ return NUMERIC_ORDER.compare(candidatePrerelease, currentPrerelease) > 0;
13
+ }
14
+ function splitPrerelease(version) {
15
+ const [withoutBuild = ""] = version.split("+");
16
+ const dash = withoutBuild.indexOf("-");
17
+ return dash === -1
18
+ ? [withoutBuild, ""]
19
+ : [withoutBuild.slice(0, dash), withoutBuild.slice(dash + 1)];
20
+ }
package/dist/main.js CHANGED
@@ -1,17 +1,20 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFile } from "node:fs/promises";
3
- import { parseArgs } from "node:util";
3
+ import { parseArgs, styleText } from "node:util";
4
4
  import { cancel } from "@clack/prompts";
5
5
  import { ClackPrompt } from "./adapters/clack-prompt.js";
6
6
  import { FsProjectFiles } from "./adapters/fs-project-files.js";
7
+ import { latestVersion } from "./adapters/npm-registry.js";
7
8
  import { dependenciesAt } from "./adapters/scaffold-dependencies.js";
8
9
  import { addApp, addPackage } from "./application/add-app.js";
9
10
  import { refreshAgentRules } from "./application/refresh-agent-rules.js";
10
11
  import { CancelledError, DEFAULT_ANSWERS, RECOMMENDED, } from "./application/workspace-answers.js";
11
12
  import { initialiseGit, install, scaffold, showNextSteps } from "./commands/create.js";
12
13
  import { MOBILE_OSES, osFor, PLATFORMS, parseChoice, parsePackageName, STARTING_PLATFORMS, } from "./domain/app-spec.js";
14
+ import { isNewer } from "./domain/version.js";
13
15
  const EXIT_FAILURE = 1;
14
16
  const EXIT_CANCELLED = 130;
17
+ const UPDATE_CHECK_TIMEOUT_MS = 1_500;
15
18
  const OPTIONS = {
16
19
  platform: { type: "string" },
17
20
  type: { type: "string" },
@@ -69,7 +72,7 @@ async function main(argv) {
69
72
  if (values.help)
70
73
  return console.log(USAGE);
71
74
  if (values.version)
72
- return console.log(await ownVersion());
75
+ return console.log((await ownManifest()).version);
73
76
  const [first, ...rest] = positionals;
74
77
  const subcommand = first === undefined ? undefined : SUBCOMMANDS[first];
75
78
  if (subcommand === undefined)
@@ -77,14 +80,30 @@ async function main(argv) {
77
80
  return subcommand(rest, values);
78
81
  }
79
82
  async function create(name, values) {
80
- const workspace = await scaffold(setupFlagsOf(name, values), promptFor(values), await ownVersion());
83
+ const manifest = await ownManifest();
84
+ const pendingNotice = updateNotice(manifest);
85
+ const workspace = await scaffold(setupFlagsOf(name, values), promptFor(values), manifest.version);
81
86
  const installation = values["skip-install"] ? "skipped" : await install(workspace);
82
87
  if (!values["disable-git"])
83
88
  await initialiseGit(workspace);
84
89
  showNextSteps(workspace, installation);
90
+ const notice = await pendingNotice;
91
+ if (notice !== null)
92
+ console.error(notice);
85
93
  if (installation === "failed")
86
94
  process.exitCode = EXIT_FAILURE;
87
95
  }
96
+ /** Asks npm while the workspace is made, and only when someone is at a terminal to read the answer. */
97
+ async function updateNotice({ name, version }) {
98
+ if (!process.stderr.isTTY)
99
+ return null;
100
+ const latest = await latestVersion(name, AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS));
101
+ return latest !== null && isNewer(latest, version) ? updateMessage(version, latest) : null;
102
+ }
103
+ function updateMessage(current, latest) {
104
+ const update = styleText("cyan", `pnpm create ovok@${latest}`);
105
+ return `${styleText("yellow", "Update available:")} ${current} → ${latest}. Next time run ${update}`;
106
+ }
88
107
  function promptFor(values) {
89
108
  const interactive = process.stdin.isTTY && process.stdout.isTTY && !values.yes;
90
109
  return interactive ? new ClackPrompt() : DEFAULT_ANSWERS;
@@ -103,9 +122,9 @@ function setupFlagsOf(name, values) {
103
122
  function typeOf(values) {
104
123
  return values.type === undefined ? null : parseChoice(values.type, MOBILE_OSES, "--type");
105
124
  }
106
- async function ownVersion() {
125
+ async function ownManifest() {
107
126
  const manifest = await readFile(new URL("../package.json", import.meta.url), "utf8");
108
- return JSON.parse(manifest).version;
127
+ return JSON.parse(manifest);
109
128
  }
110
129
  try {
111
130
  await main(process.argv.slice(2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-ovok",
3
- "version": "0.1.0-dev.22",
3
+ "version": "0.1.0-dev.28",
4
4
  "description": "Create a Turborepo with Ovok web and mobile apps, and add more apps through turbo gen.",
5
5
  "license": "MIT",
6
6
  "type": "module",