reset-framework-cli 1.0.2 → 1.1.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reset-framework-cli",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "type": "module",
5
5
  "description": "Command-line tooling for Reset Framework.",
6
6
  "license": "MIT",
@@ -11,9 +11,9 @@
11
11
  "node": ">=20.19.0"
12
12
  },
13
13
  "dependencies": {
14
- "@reset-framework/native": "1.0.2",
15
- "@reset-framework/schema": "1.0.2",
16
- "@reset-framework/sdk": "1.0.2"
14
+ "@reset-framework/native": "1.1.0",
15
+ "@reset-framework/schema": "1.1.0",
16
+ "@reset-framework/sdk": "1.1.0"
17
17
  },
18
18
  "bin": {
19
19
  "reset-framework-cli": "src/index.js"
package/src/lib/output.js CHANGED
@@ -1,9 +1,99 @@
1
- import { cp, mkdir, rm } from "node:fs/promises"
1
+ import path from "node:path"
2
+ import { cp, mkdir, rm, writeFile } from "node:fs/promises"
2
3
  import { existsSync } from "node:fs"
3
4
 
4
5
  import { logger } from "./logger.js"
5
6
  import { runCommand } from "./process.js"
6
7
 
8
+ function escapePlistString(value) {
9
+ return value
10
+ .replaceAll("&", "&")
11
+ .replaceAll("<", "&lt;")
12
+ .replaceAll(">", "&gt;")
13
+ }
14
+
15
+ function serializePlistValue(value, indentLevel = 1) {
16
+ const indent = " ".repeat(indentLevel)
17
+
18
+ if (Array.isArray(value)) {
19
+ if (value.length === 0) {
20
+ return `${indent}<array/>\n`
21
+ }
22
+
23
+ return (
24
+ `${indent}<array>\n` +
25
+ value.map((entry) => serializePlistValue(entry, indentLevel + 1)).join("") +
26
+ `${indent}</array>\n`
27
+ )
28
+ }
29
+
30
+ if (typeof value === "object" && value !== null) {
31
+ const entries = Object.entries(value)
32
+ if (entries.length === 0) {
33
+ return `${indent}<dict/>\n`
34
+ }
35
+
36
+ return (
37
+ `${indent}<dict>\n` +
38
+ entries
39
+ .map(
40
+ ([key, entryValue]) =>
41
+ `${" ".repeat(indentLevel + 1)}<key>${escapePlistString(key)}</key>\n${serializePlistValue(entryValue, indentLevel + 1)}`
42
+ )
43
+ .join("") +
44
+ `${indent}</dict>\n`
45
+ )
46
+ }
47
+
48
+ if (typeof value === "boolean") {
49
+ return `${indent}<${value ? "true" : "false"}/>\n`
50
+ }
51
+
52
+ if (typeof value === "number") {
53
+ return `${indent}<integer>${value}</integer>\n`
54
+ }
55
+
56
+ return `${indent}<string>${escapePlistString(String(value))}</string>\n`
57
+ }
58
+
59
+ function buildMacOSInfoPlist(config) {
60
+ const urlTypes = Array.isArray(config.protocols?.schemes)
61
+ ? config.protocols.schemes.map((schemeConfig) => ({
62
+ CFBundleTypeRole: schemeConfig.role ?? "Viewer",
63
+ CFBundleURLName:
64
+ typeof schemeConfig.name === "string" && schemeConfig.name.trim() !== ""
65
+ ? schemeConfig.name
66
+ : `${config.appId}.${schemeConfig.scheme}`,
67
+ CFBundleURLSchemes: [schemeConfig.scheme]
68
+ }))
69
+ : []
70
+
71
+ const plist = {
72
+ CFBundleDevelopmentRegion: "English",
73
+ CFBundleDisplayName: config.productName,
74
+ CFBundleExecutable: "reset-framework",
75
+ CFBundleIdentifier: config.appId,
76
+ CFBundleInfoDictionaryVersion: "6.0",
77
+ CFBundleName: config.productName,
78
+ CFBundlePackageType: "APPL",
79
+ CFBundleShortVersionString: config.version,
80
+ CFBundleVersion: config.version,
81
+ CSResourcesFileMapped: true
82
+ }
83
+
84
+ if (urlTypes.length > 0) {
85
+ plist.CFBundleURLTypes = urlTypes
86
+ }
87
+
88
+ return (
89
+ `<?xml version="1.0" encoding="UTF-8"?>\n` +
90
+ `<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
91
+ `<plist version="1.0">\n` +
92
+ serializePlistValue(plist, 0) +
93
+ `</plist>\n`
94
+ )
95
+ }
96
+
7
97
  export async function stageMacOSAppBundle(frameworkBuildPaths, appPaths, config, options = {}) {
8
98
  const { dryRun = false, outputPaths, configPath } = options
9
99
 
@@ -40,6 +130,11 @@ export async function stageMacOSAppBundle(frameworkBuildPaths, appPaths, config,
40
130
  await cp(configPath, outputPaths.bundledConfigPath, { force: true })
41
131
  await rm(outputPaths.bundledFrontendDir, { recursive: true, force: true })
42
132
  await cp(outputPaths.frontendDistDir, outputPaths.bundledFrontendDir, { recursive: true })
133
+ await writeFile(
134
+ path.join(outputPaths.appBundlePath, "Contents", "Info.plist"),
135
+ buildMacOSInfoPlist(config),
136
+ "utf8"
137
+ )
43
138
 
44
139
  return outputPaths
45
140
  }
@@ -76,6 +76,29 @@ function normalizeStyling(value) {
76
76
  throw new Error("project.styling must be either 'css' or 'tailwindcss'")
77
77
  }
78
78
 
79
+ function normalizeProtocolScheme(value) {
80
+ if (typeof value !== "string" || value.trim() === "") {
81
+ throw new Error("protocols.schemes[].scheme must be a non-empty string")
82
+ }
83
+
84
+ const normalized = value.trim().toLowerCase()
85
+ if (!/^[a-z][a-z0-9+.-]*$/.test(normalized)) {
86
+ throw new Error(
87
+ "protocols.schemes[].scheme must start with a letter and only contain letters, numbers, '+', '-', and '.'"
88
+ )
89
+ }
90
+
91
+ return normalized
92
+ }
93
+
94
+ function normalizeProtocolRole(value) {
95
+ if (value === "Editor" || value === "Viewer" || value === "Shell" || value === "None") {
96
+ return value
97
+ }
98
+
99
+ throw new Error("protocols.schemes[].role must be one of Editor, Viewer, Shell, None")
100
+ }
101
+
79
102
  function toTitleCase(value) {
80
103
  return value
81
104
  .split(/[^a-zA-Z0-9]+/)
@@ -290,7 +313,10 @@ export function validateResetConfig(rawConfig) {
290
313
  const build = optionalObject(rawConfig, "build")
291
314
  const project = optionalObject(rawConfig, "project")
292
315
  const security = optionalObject(rawConfig, "security")
316
+ const protocols = optionalObject(rawConfig, "protocols")
293
317
  const windowConfig = optionalObject(rawConfig, "window")
318
+ const rawProtocolSchemes = Array.isArray(protocols.schemes) ? protocols.schemes : []
319
+ const seenProtocolSchemes = new Set()
294
320
 
295
321
  return {
296
322
  ...rawConfig,
@@ -323,6 +349,32 @@ export function validateResetConfig(rawConfig) {
323
349
  return value
324
350
  })
325
351
  : []
352
+ },
353
+ protocols: {
354
+ schemes: rawProtocolSchemes.map((entry) => {
355
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
356
+ throw new Error("protocols.schemes entries must be objects")
357
+ }
358
+
359
+ const scheme = normalizeProtocolScheme(entry.scheme)
360
+ if (seenProtocolSchemes.has(scheme)) {
361
+ throw new Error(`Duplicate protocol scheme '${scheme}'`)
362
+ }
363
+
364
+ seenProtocolSchemes.add(scheme)
365
+
366
+ return {
367
+ scheme,
368
+ name:
369
+ typeof entry.name === "string" && entry.name.trim() !== ""
370
+ ? entry.name
371
+ : scheme,
372
+ role:
373
+ entry.role === undefined
374
+ ? "Viewer"
375
+ : normalizeProtocolRole(entry.role)
376
+ }
377
+ })
326
378
  }
327
379
  }
328
380
  }
@@ -51,5 +51,8 @@
51
51
  "updater.*",
52
52
  "net.*"
53
53
  ]
54
+ },
55
+ "protocols": {
56
+ "schemes": []
54
57
  }
55
58
  }