code2app 0.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/README.md +111 -0
- package/assets/icon-source.png +0 -0
- package/bin/cli.js +248 -0
- package/dist/index.html +24 -0
- package/docs/APP_STORES.md +129 -0
- package/docs/BUILDING.md +134 -0
- package/docs/LOCAL_APPS.md +116 -0
- package/docs/MOBILE.md +78 -0
- package/docs/OAUTH.md +76 -0
- package/package.json +66 -0
- package/profiles/README.md +56 -0
- package/profiles/example.json +37 -0
- package/scripts/build-sidecar.mjs +80 -0
- package/scripts/configure.mjs +222 -0
- package/scripts/generate-icons.mjs +53 -0
- package/scripts/generate-placeholder-icons.mjs +186 -0
- package/scripts/lib/png.mjs +141 -0
- package/scripts/profile.mjs +147 -0
- package/src-tauri/Cargo.lock +5832 -0
- package/src-tauri/Cargo.toml +47 -0
- package/src-tauri/build.rs +3 -0
- package/src-tauri/capabilities/default.json +15 -0
- package/src-tauri/capabilities/remote.json +17 -0
- package/src-tauri/icons/128x128.png +0 -0
- package/src-tauri/icons/128x128@2x.png +0 -0
- package/src-tauri/icons/32x32.png +0 -0
- package/src-tauri/icons/64x64.png +0 -0
- package/src-tauri/icons/Square107x107Logo.png +0 -0
- package/src-tauri/icons/Square142x142Logo.png +0 -0
- package/src-tauri/icons/Square150x150Logo.png +0 -0
- package/src-tauri/icons/Square284x284Logo.png +0 -0
- package/src-tauri/icons/Square30x30Logo.png +0 -0
- package/src-tauri/icons/Square310x310Logo.png +0 -0
- package/src-tauri/icons/Square44x44Logo.png +0 -0
- package/src-tauri/icons/Square71x71Logo.png +0 -0
- package/src-tauri/icons/Square89x89Logo.png +0 -0
- package/src-tauri/icons/StoreLogo.png +0 -0
- package/src-tauri/icons/icon.icns +0 -0
- package/src-tauri/icons/icon.ico +0 -0
- package/src-tauri/icons/icon.png +0 -0
- package/src-tauri/src/generated_config.rs +22 -0
- package/src-tauri/src/lib.rs +212 -0
- package/src-tauri/src/main.rs +6 -0
- package/src-tauri/tauri.conf.json +84 -0
- package/vitest.config.mjs +35 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
// Loading + validation for profiles/<name>.json, shared by every script that
|
|
2
|
+
// needs one (configure, icon generation, the scaffolding CLI). Kept separate so
|
|
3
|
+
// there is exactly one definition of what a profile means and what a missing or
|
|
4
|
+
// contradictory field does — the scripts downstream can then assume a profile
|
|
5
|
+
// they were handed is already coherent.
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
/** Profile fields every mode needs, whatever else the profile turns on. */
|
|
11
|
+
const ALWAYS_REQUIRED = ["appName", "identifier", "version"];
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolves which profile to use: an explicit `--profile <name>`, else
|
|
15
|
+
* $WRAPPER_PROFILE, else the single profile in profiles/ if there is exactly
|
|
16
|
+
* one, else an error naming the candidates. The "exactly one" case is what lets
|
|
17
|
+
* a scaffolded copy (which has one profile, its own) skip the flag entirely.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveProfileName(rootDir, argv = process.argv.slice(2)) {
|
|
20
|
+
const flagIndex = argv.indexOf("--profile");
|
|
21
|
+
if (flagIndex !== -1) {
|
|
22
|
+
const name = argv[flagIndex + 1];
|
|
23
|
+
if (!name || name.startsWith("--")) {
|
|
24
|
+
throw new Error("--profile needs a profile name, e.g. --profile about-system");
|
|
25
|
+
}
|
|
26
|
+
return name;
|
|
27
|
+
}
|
|
28
|
+
if (process.env.WRAPPER_PROFILE) return process.env.WRAPPER_PROFILE;
|
|
29
|
+
|
|
30
|
+
const profilesDir = path.join(rootDir, "profiles");
|
|
31
|
+
const names = existsSync(profilesDir)
|
|
32
|
+
? readdirSync(profilesDir)
|
|
33
|
+
.filter((f) => f.endsWith(".json"))
|
|
34
|
+
.map((f) => f.replace(/\.json$/, ""))
|
|
35
|
+
.filter((n) => n !== "example")
|
|
36
|
+
: [];
|
|
37
|
+
if (names.length === 1) return names[0];
|
|
38
|
+
// A copy of this package that hasn't been given an identity yet still needs
|
|
39
|
+
// to configure and build, so the untouched template is the last fallback.
|
|
40
|
+
if (names.length === 0) {
|
|
41
|
+
if (existsSync(path.join(profilesDir, "example.json"))) return "example";
|
|
42
|
+
throw new Error(
|
|
43
|
+
"no profile to use: profiles/ is empty. Copy example.json to " +
|
|
44
|
+
"profiles/<your-app>.json and fill it in (see profiles/README.md).",
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
throw new Error(
|
|
48
|
+
`profiles/ has ${names.length} profiles (${names.join(", ")}) — pick one with ` +
|
|
49
|
+
"--profile <name> or WRAPPER_PROFILE=<name>.",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Reads and validates profiles/<name>.json, filling in every default. */
|
|
54
|
+
export function loadProfile(rootDir, profileName) {
|
|
55
|
+
const profilePath = path.join(rootDir, "profiles", `${profileName}.json`);
|
|
56
|
+
if (!existsSync(profilePath)) {
|
|
57
|
+
throw new Error(`no such profile: ${path.relative(rootDir, profilePath)}`);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
let profile;
|
|
61
|
+
try {
|
|
62
|
+
profile = JSON.parse(readFileSync(profilePath, "utf8"));
|
|
63
|
+
} catch (cause) {
|
|
64
|
+
throw new Error(`profiles/${profileName}.json is not valid JSON: ${cause.message}`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const field of ALWAYS_REQUIRED) {
|
|
68
|
+
if (!profile[field]) {
|
|
69
|
+
throw new Error(`profiles/${profileName}.json is missing required field "${field}"`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const mode = profile.mode ?? "remote";
|
|
74
|
+
if (mode !== "remote" && mode !== "local") {
|
|
75
|
+
throw new Error(`profiles/${profileName}.json has unknown mode "${mode}" (expected "remote" or "local")`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (mode === "remote") {
|
|
79
|
+
if (!profile.url) {
|
|
80
|
+
throw new Error(`profiles/${profileName}.json is mode "remote" and needs a "url"`);
|
|
81
|
+
}
|
|
82
|
+
if (!/^https:\/\//.test(profile.url) && !/^http:\/\/localhost[:/]/.test(profile.url)) {
|
|
83
|
+
throw new Error(
|
|
84
|
+
`profiles/${profileName}.json's url must be https:// (http://localhost is allowed for local dev), got "${profile.url}"`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
} else if (profile.url) {
|
|
88
|
+
// A local-mode profile that also carries a url would silently ignore it,
|
|
89
|
+
// which reads as "the app loads that site" to anyone skimming the profile.
|
|
90
|
+
throw new Error(
|
|
91
|
+
`profiles/${profileName}.json is mode "local" but also sets "url" — local mode loads ` +
|
|
92
|
+
"the bundled frontend in dist/, so remove the url or switch the mode to \"remote\".",
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (profile.sidecar && !profile.sidecar.name) {
|
|
97
|
+
throw new Error(`profiles/${profileName}.json has a "sidecar" block with no "name"`);
|
|
98
|
+
}
|
|
99
|
+
if (profile.sidecar?.build && !profile.sidecar.build.includes("{out}")) {
|
|
100
|
+
// Without the placeholder the command would write wherever it likes and the
|
|
101
|
+
// bundler would then fail to find a binary under the name it expects.
|
|
102
|
+
throw new Error(
|
|
103
|
+
`profiles/${profileName}.json's sidecar.build must contain "{out}" — that's where ` +
|
|
104
|
+
"scripts/build-sidecar.mjs substitutes the path Tauri looks for the binary at.",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
if (profile.updater && !(profile.updater.pubkey && profile.updater.endpoints?.length)) {
|
|
108
|
+
// Half-configured updates are the failure mode worth catching early: the
|
|
109
|
+
// plugin panics at startup without a pubkey, and polls nothing without an
|
|
110
|
+
// endpoint.
|
|
111
|
+
throw new Error(
|
|
112
|
+
`profiles/${profileName}.json's "updater" needs both a "pubkey" (from ` +
|
|
113
|
+
'`npx @tauri-apps/cli@2 signer generate`) and a non-empty "endpoints" list.',
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
if (profile.sidecar && mode !== "local") {
|
|
117
|
+
// The sidecar command is only reachable from locally bundled content (see
|
|
118
|
+
// src-tauri/src/lib.rs's origin guard), so a remote-mode profile asking for
|
|
119
|
+
// one has configured something it can never call.
|
|
120
|
+
throw new Error(
|
|
121
|
+
`profiles/${profileName}.json pairs a "sidecar" with mode "remote" — the sidecar is only ` +
|
|
122
|
+
"callable from the bundled frontend, so it needs mode \"local\".",
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...profile,
|
|
128
|
+
name: profileName,
|
|
129
|
+
path: profilePath,
|
|
130
|
+
mode,
|
|
131
|
+
productName: profile.productName ?? profile.appName,
|
|
132
|
+
window: {
|
|
133
|
+
width: 1280,
|
|
134
|
+
height: 800,
|
|
135
|
+
minWidth: 480,
|
|
136
|
+
minHeight: 480,
|
|
137
|
+
fullscreen: false,
|
|
138
|
+
resizable: true,
|
|
139
|
+
title: profile.appName,
|
|
140
|
+
...profile.window,
|
|
141
|
+
},
|
|
142
|
+
// No default args: what a CLI needs to be asked for is the app author's
|
|
143
|
+
// call, and a guessed flag would be a runtime failure, not a config error.
|
|
144
|
+
sidecar: profile.sidecar ? { args: [], ...profile.sidecar } : null,
|
|
145
|
+
trustedOrigins: profile.trustedOrigins ?? [],
|
|
146
|
+
};
|
|
147
|
+
}
|