create-catalyst-app 1.1.0 → 1.2.0-canary.2

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
@@ -51,6 +51,19 @@ To serve the production build, execute:
51
51
  npm run serve
52
52
  ```
53
53
 
54
+ ## Android and iOS
55
+
56
+ Every app is ready to run as a native app. `config/config.json` gets `WEBVIEW_CONFIG.appId` (`com.example.<project name>`) and `appName` from the project name, and the app gets a placeholder `public/icon.png`.
57
+
58
+ ```bash
59
+ npm run android # run on an Android emulator or connected phone
60
+ npm run ios # run on an iOS simulator or connected iPhone (macOS)
61
+ npm run android:release # signed App Bundle for Google Play
62
+ npm run ios:release # signed IPA for App Store Connect
63
+ ```
64
+
65
+ The first run installs what is missing after asking. Before a release, replace the `com.example.` app id with your own and set `WEBVIEW_CONFIG.url` to your deployed site. See [Universal App Deployment](https://catalyst.1mg.com/content/Deployment/universal-app-deployment).
66
+
54
67
  ## Documentation
55
68
 
56
69
  Explore the complete documentation at [https://catalyst.1mg.com](https://catalyst.1mg.com).
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "create-catalyst-app",
3
3
  "bin": "scripts/cli.cjs",
4
- "version": "1.1.0",
4
+ "version": "1.2.0-canary.2",
5
5
  "engines": {
6
6
  "node": ">=20.12.0"
7
7
  },
@@ -0,0 +1,77 @@
1
+ // The app id and name every new app gets in config/config.json, derived from
2
+ // the project name, so they are visible (and easy to change) from day one.
3
+ // A release build refuses the com.example.* id until it is replaced.
4
+ const fs = require("fs")
5
+ const path = require("path")
6
+
7
+ // Java keywords and literals cannot be package-name segments.
8
+ const JAVA_RESERVED = new Set(
9
+ (
10
+ "abstract assert boolean break byte case catch char class const continue default do double else enum " +
11
+ "extends false final finally float for goto if implements import instanceof int interface long native " +
12
+ "new null package private protected public return short static strictfp super switch synchronized this " +
13
+ "throw throws transient true try void volatile while"
14
+ ).split(" ")
15
+ )
16
+
17
+ /** "@scope/my-shop" -> "com.example.myshop": valid on Android and iOS. */
18
+ function deriveAppId(projectName) {
19
+ const base = String(projectName || "")
20
+ .split("/")
21
+ .pop()
22
+ .toLowerCase()
23
+ .replace(/[^a-z0-9]/g, "")
24
+ let segment = base || "app"
25
+ if (/^[0-9]/.test(segment)) segment = `app${segment}`
26
+ if (JAVA_RESERVED.has(segment)) segment = `${segment}app`
27
+ return `com.example.${segment}`
28
+ }
29
+
30
+ /** "my-cool_app" -> "My Cool App": the name shown under the app icon. */
31
+ function deriveAppName(projectName) {
32
+ const words = String(projectName || "")
33
+ .split("/")
34
+ .pop()
35
+ .split(/[^A-Za-z0-9]+/)
36
+ .filter(Boolean)
37
+ if (words.length === 0) return "Catalyst App"
38
+ return words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ")
39
+ }
40
+
41
+ /**
42
+ * Set WEBVIEW_CONFIG.appId and appName in the new app's config/config.json
43
+ * from the project name. The template carries both keys with placeholder
44
+ * values; only those two values change, so the file keeps its layout. A
45
+ * config without them gets them added under WEBVIEW_CONFIG.
46
+ *
47
+ * @param {string} projectDir
48
+ * @param {string} projectName
49
+ * @returns {boolean} whether the file was written
50
+ */
51
+ function applyAppIdentity(projectDir, projectName) {
52
+ const file = path.join(projectDir, "config", "config.json")
53
+ let text
54
+ let config
55
+ try {
56
+ text = fs.readFileSync(file, "utf8")
57
+ config = JSON.parse(text)
58
+ } catch {
59
+ return false
60
+ }
61
+ const wv = config.WEBVIEW_CONFIG
62
+ if (!wv || typeof wv !== "object" || Array.isArray(wv)) return false
63
+ const appId = deriveAppId(projectName)
64
+ const appName = deriveAppName(projectName)
65
+ if (typeof wv.appId === "string" && typeof wv.appName === "string") {
66
+ text = text
67
+ .replace(/("appId"\s*:\s*)"[^"]*"/, `$1${JSON.stringify(appId)}`)
68
+ .replace(/("appName"\s*:\s*)"[^"]*"/, `$1${JSON.stringify(appName)}`)
69
+ } else {
70
+ config.WEBVIEW_CONFIG = { appId, appName, ...wv }
71
+ text = JSON.stringify(config, null, 4) + "\n"
72
+ }
73
+ fs.writeFileSync(file, text)
74
+ return true
75
+ }
76
+
77
+ module.exports = { deriveAppId, deriveAppName, applyAppIdentity }
package/scripts/cli.cjs CHANGED
@@ -21,6 +21,7 @@ const {
21
21
  resolveOutputMode,
22
22
  getDebugEnvInfo,
23
23
  } = require("./errors.cjs")
24
+ const { applyAppIdentity } = require("./appIdentity.cjs")
24
25
  const outputMode = resolveOutputMode(process.argv)
25
26
  // Colored (red) prefix only applies in default mode — verbose/debug boxes
26
27
  // own their coloring internally (see errors.cjs#box).
@@ -157,6 +158,8 @@ const program = new Commander.Command()
157
158
 
158
159
  extractSubdirectory(packageFilePath)
159
160
  createGitignore(projectName)
161
+ // appId and appName in config/config.json, from the project name.
162
+ applyAppIdentity(projectPath, projectName)
160
163
 
161
164
  // npm's install output is hundreds of lines nobody reads
162
165
  // when it works. Fold it behind a spinner, and print it in
@@ -568,7 +571,16 @@ function deleteDirectory(dirPath) {
568
571
 
569
572
  // Function to create a .gitignore file with the hardcoded patterns
570
573
  function createGitignore(projectName) {
571
- const gitiIgnorePatterns = ["node_modules", "build", "logs"]
574
+ // credentials/ holds signing keys, .catalyst/ per-machine CLI state, and
575
+ // build-output/ the signed store builds.
576
+ const gitiIgnorePatterns = [
577
+ "node_modules",
578
+ "build",
579
+ "logs",
580
+ "credentials/",
581
+ ".catalyst/",
582
+ "build-output/",
583
+ ]
572
584
 
573
585
  const gitignorePath = `${process.cwd()}${path.sep}${projectName}${path.sep}.gitignore`
574
586
 
@@ -48,6 +48,12 @@ function generateDocs() {
48
48
  // Merge into the shared errors/index.json rather than overwriting it —
49
49
  // catalyst-core's own generateDocs.js owns every other category.
50
50
  const index = readExistingIndex()
51
+ // Drop this package's own entries first, so a code removed from
52
+ // ERROR_DEFINITIONS does not linger in the index.
53
+ const ownedCategories = new Set(Object.values(ERROR_DEFINITIONS).map((d) => d.category))
54
+ for (const code of Object.keys(index)) {
55
+ if (ownedCategories.has(index[code].category)) delete index[code]
56
+ }
51
57
 
52
58
  for (const [code, def] of Object.entries(ERROR_DEFINITIONS)) {
53
59
  const dir = path.join(ERRORS_DIR, def.category)
@@ -7,6 +7,8 @@
7
7
  "API_URL": "",
8
8
  "CLIENT_ENV_VARIABLES": ["API_URL"],
9
9
  "WEBVIEW_CONFIG": {
10
+ "appId": "com.example.app",
11
+ "appName": "Catalyst App",
10
12
  "LOCAL_IP": "localhost",
11
13
  "port": "3005",
12
14
  "appInfo": "",
@@ -15,6 +17,13 @@
15
17
  },
16
18
  "ios": {
17
19
  "buildType": "debug"
20
+ },
21
+ "splashScreen": {
22
+ "backgroundColor": "#0b0a14",
23
+ "minDuration": 7000,
24
+ "imageWidth": 144,
25
+ "imageHeight": 144,
26
+ "cornerRadius": 0
18
27
  }
19
28
  }
20
29
  }
Binary file
@@ -3,54 +3,75 @@
3
3
  <head>
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="light dark" />
6
7
  <title>Offline</title>
7
8
  <style>
9
+ :root {
10
+ --paper: #f5f4ff;
11
+ --ink: #14132a;
12
+ --muted: #4b4a6b;
13
+ --accent: #5353dc;
14
+ }
15
+ @media (prefers-color-scheme: dark) {
16
+ :root {
17
+ --paper: #0b0a14;
18
+ --ink: #f3f2ff;
19
+ --muted: #a9a8c9;
20
+ --accent: #a0a0ff;
21
+ }
22
+ }
8
23
  body {
9
24
  margin: 0;
10
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
11
- background: #282c34;
12
- color: #ffffff;
13
- min-height: 100vh;
25
+ font-family:
26
+ system-ui,
27
+ -apple-system,
28
+ "Segoe UI",
29
+ sans-serif;
30
+ background: var(--paper);
31
+ color: var(--ink);
32
+ min-height: 100dvh;
14
33
  display: flex;
15
34
  align-items: center;
16
- justify-content: center;
17
- text-align: center;
18
- font-size: calc(10px + 2vmin);
35
+ padding: 0 28px;
36
+ box-sizing: border-box;
37
+ -webkit-font-smoothing: antialiased;
19
38
  }
20
39
  .card {
21
- display: flex;
22
- flex-direction: column;
23
- align-items: center;
24
- gap: 12px;
25
- padding: 0 24px;
26
40
  max-width: 440px;
27
41
  }
28
42
  h1 {
29
43
  margin: 0;
30
- font-size: 2rem;
31
- font-weight: 700;
44
+ font-size: clamp(2.5rem, 11vw, 3.5rem);
45
+ font-weight: 800;
46
+ line-height: 1.05;
47
+ letter-spacing: -0.04em;
32
48
  }
33
49
  p {
34
- margin: 0;
35
- color: #d8e0f7;
36
- line-height: 1.4;
50
+ margin: 16px 0 0;
51
+ color: var(--muted);
52
+ font-size: 1.0625rem;
53
+ line-height: 1.5;
37
54
  }
38
55
  a.retry {
39
- color: #61dafb;
40
- font-weight: 600;
56
+ display: inline-block;
57
+ margin-top: 16px;
58
+ padding: 12px 0;
59
+ color: var(--accent);
60
+ font-weight: 650;
41
61
  text-decoration: none;
42
- transition: color 0.2s ease;
43
62
  }
44
- a.retry:hover {
45
- color: #9ae3fb;
63
+ a.retry:focus-visible {
64
+ outline: 2px solid var(--accent);
65
+ outline-offset: 3px;
66
+ border-radius: 4px;
46
67
  }
47
68
  </style>
48
69
  </head>
49
70
  <body>
50
71
  <div class="card">
51
- <h1>You're Offline</h1>
52
- <p>Please check your connection and try again.</p>
53
- <a class="retry" href="catalyst://retry">Try Again</a>
72
+ <h1>You're offline.</h1>
73
+ <p>Check your connection and try again.</p>
74
+ <a class="retry" href="catalyst://retry">Try again</a>
54
75
  </div>
55
76
  </body>
56
77
  </html>