@legendapp/spark 0.0.1-next.3 → 0.0.1-next.4
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/camera.ts +13 -0
- package/package.json +21 -11
- package/patches/camera/react-native-nitro-image.patch +320 -0
- package/patches/camera/react-native-nitro-modules.patch +50 -0
- package/patches/camera/react-native-vision-camera.patch +2662 -0
- package/patches/camera/upstream.json +22 -0
- package/patches/dependencies.json +8 -0
- package/patches/react-native-enriched-markdown@1.0.2.patch +8684 -0
- package/postinstall.cjs +1 -0
- package/ui-swipe.ts +1 -0
- package/vendor/node_modules/@legendapp/spark-ai/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-audio/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-auth-session/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/build.js +62 -11
- package/vendor/node_modules/@legendapp/spark-cli/dist/cli-arguments.js +2 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/commands.js +3 -2
- package/vendor/node_modules/@legendapp/spark-cli/dist/create.js +6 -3
- package/vendor/node_modules/@legendapp/spark-cli/dist/credentials.js +41 -26
- package/vendor/node_modules/@legendapp/spark-cli/dist/dependency-pins.js +132 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev-command.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/dev.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/errors.js +100 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/examples.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/expo-native.cjs +5 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/helpers.js +1 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/index.js +43 -7
- package/vendor/node_modules/@legendapp/spark-cli/dist/json.js +16 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/native-patch-preflight.js +3 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/process.js +7 -0
- package/vendor/node_modules/@legendapp/spark-cli/dist/project.js +27 -22
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.js +2 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/release.json +19 -19
- package/vendor/node_modules/@legendapp/spark-cli/dist/universal.cjs +2 -1
- package/vendor/node_modules/@legendapp/spark-cli/dist/windows.js +1 -8
- package/vendor/node_modules/@legendapp/spark-cli/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-cli/templates/blank-typescript/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/universal/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-cli/templates/windows/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-clipboard/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-codex/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-commands/package.json +5 -5
- package/vendor/node_modules/@legendapp/spark-context-menu/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-app/package.json +1 -1
- package/vendor/node_modules/@legendapp/spark-desktop-app/src/contracts/menu.ts +6 -4
- package/vendor/node_modules/@legendapp/spark-desktop-config/config.cjs +2 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/expo.cjs +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.cjs +106 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/native-patches.d.cts +1 -0
- package/vendor/node_modules/@legendapp/spark-desktop-config/package.json +5 -4
- package/vendor/node_modules/@legendapp/spark-desktop-host/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-links/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-shortcuts/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-desktop-windows/macos/window-manager/RNWindowManager.mm +39 -0
- package/vendor/node_modules/@legendapp/spark-desktop-windows/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/api.ts +3 -1
- package/vendor/node_modules/@legendapp/spark-desktop-windows/src/macos-adapter.ts +1 -1
- package/vendor/node_modules/@legendapp/spark-documents/package.json +7 -7
- package/vendor/node_modules/@legendapp/spark-drag-drop/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-file-dialog/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-file-system/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-global-shortcuts/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-message-dialog/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-native-menu/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-notifications/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-notifications/windows/SparkNotifications/SparkNotifications.h +16 -11
- package/vendor/node_modules/@legendapp/spark-processes/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-secure-storage/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-settings/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-settings-ui/package.json +4 -4
- package/vendor/node_modules/@legendapp/spark-sqlite/package.json +3 -3
- package/vendor/node_modules/@legendapp/spark-system/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-tray/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-ui/macos/appkit-split-view/RNSidebarSplitViewComponent.mm +263 -26
- package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.h +10 -0
- package/vendor/node_modules/@legendapp/spark-ui/macos/swipe-actions/RNSwipeActions.mm +422 -0
- package/vendor/node_modules/@legendapp/spark-ui/package.json +3 -2
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/SidebarSplitViewNativeComponent.ts +6 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/appkit-split-view/index.tsx +20 -11
- package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/SwipeActionsNativeComponent.ts +11 -0
- package/vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/index.tsx +41 -0
- package/vendor/node_modules/@legendapp/spark-updates/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-webview/package.json +2 -2
- package/vendor/node_modules/@legendapp/spark-window-options/package.json +1 -1
package/postinstall.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require("./vendor/node_modules/@legendapp/spark-desktop-config/native-patches.cjs").prepareNativePatches(__dirname);
|
package/ui-swipe.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./vendor/node_modules/@legendapp/spark-ui/src/swipe-actions/index.tsx";
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@legendapp/spark-ai",
|
|
3
|
-
"version": "0.0.1-next.
|
|
3
|
+
"version": "0.0.1-next.4",
|
|
4
4
|
"private": true,
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@legendapp/spark-desktop-app": "0.0.1-next.
|
|
9
|
-
"@legendapp/spark-processes": "0.0.1-next.
|
|
8
|
+
"@legendapp/spark-desktop-app": "0.0.1-next.4",
|
|
9
|
+
"@legendapp/spark-processes": "0.0.1-next.4"
|
|
10
10
|
},
|
|
11
11
|
"peerDependencies": {
|
|
12
12
|
"react": "19.1.4",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@legendapp/spark-audio",
|
|
3
|
-
"version": "0.0.1-next.
|
|
3
|
+
"version": "0.0.1-next.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"react-native": "src/index",
|
|
@@ -47,6 +47,6 @@
|
|
|
47
47
|
},
|
|
48
48
|
"private": true,
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@legendapp/spark-desktop-app": "0.0.1-next.
|
|
50
|
+
"@legendapp/spark-desktop-app": "0.0.1-next.4"
|
|
51
51
|
}
|
|
52
52
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@legendapp/spark-auth-session",
|
|
3
|
-
"version": "0.0.1-next.
|
|
3
|
+
"version": "0.0.1-next.4",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"main": "src/index",
|
|
6
6
|
"react-native": "src/index",
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"src"
|
|
9
9
|
],
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@legendapp/spark-desktop-links": "0.0.1-next.
|
|
11
|
+
"@legendapp/spark-desktop-links": "0.0.1-next.4",
|
|
12
12
|
"base64-js": "1.5.1",
|
|
13
|
-
"@legendapp/spark-desktop-app": "0.0.1-next.
|
|
13
|
+
"@legendapp/spark-desktop-app": "0.0.1-next.4"
|
|
14
14
|
},
|
|
15
15
|
"peerDependencies": {
|
|
16
16
|
"react-native": "0.81.6",
|
|
@@ -12,10 +12,11 @@ import path from "node:path";
|
|
|
12
12
|
import { registerRuntime } from "./local.js";
|
|
13
13
|
import { binary, doctor, run } from "./commands.js";
|
|
14
14
|
import { prepareConfig, entryFile, digest, hashFiles, nativePreparationInputs, nativeProjectFingerprint, localSigningIdentity, nativePackages, readJson, runtimeFor, selection, stateFile, writeJson, validateBuildModules, goConfigurationIssues, } from "./project.js";
|
|
15
|
-
export async function analyze(root
|
|
15
|
+
export async function analyze(root) {
|
|
16
16
|
assertNativePatchPreflight(root);
|
|
17
17
|
if (projectPlatform(root) === "windows")
|
|
18
18
|
throw new Error("Windows production analysis is not implemented; use spark build --dev.");
|
|
19
|
+
const packages = nativePackages(root, { applyExcludes: false });
|
|
19
20
|
prepareConfig(root);
|
|
20
21
|
const dir = stateFile(root, "analysis");
|
|
21
22
|
mkdirSync(dir, { recursive: true });
|
|
@@ -57,24 +58,70 @@ export async function analyze(root, packages = nativePackages(root)) {
|
|
|
57
58
|
(!source.includes("/node_modules/") || /["']@react-native-runtimes\/core["']/.test(readFileSync(source, "utf8")))) });
|
|
58
59
|
await exportBundle({ SPARK_RUNTIME_SOURCES: runtimeSources });
|
|
59
60
|
const map = readJson(path.join(dir, "app.map"));
|
|
61
|
+
const sourceFile = (source) => source.includes("\0") ? undefined
|
|
62
|
+
: [path.resolve(root, source), path.resolve(root, source.replace(/^\//, ""))].find(file => existsSync(file));
|
|
60
63
|
const used = new Set();
|
|
61
64
|
for (const source of map.sources) {
|
|
65
|
+
const file = sourceFile(source);
|
|
66
|
+
const resolved = file ? realpathSync(file) : source;
|
|
62
67
|
for (const pkg of packages) {
|
|
63
68
|
// Metro's source map can contain absolute paths or project-relative node_modules paths.
|
|
64
69
|
if (source.startsWith(pkg.root + "/") ||
|
|
70
|
+
resolved.startsWith(pkg.root + "/") ||
|
|
65
71
|
source.includes(`/node_modules/${pkg.name}/`) ||
|
|
66
72
|
source.startsWith(`node_modules/${pkg.name}/`))
|
|
67
73
|
used.add(pkg.name);
|
|
68
74
|
}
|
|
69
75
|
}
|
|
70
76
|
const config = readAppConfig(root);
|
|
71
|
-
const
|
|
77
|
+
const explicitExcluded = config.expo?.autolinking?.exclude ?? [];
|
|
78
|
+
const include = config.expo?.extra?.spark?.include ?? [];
|
|
79
|
+
const result = selection(packages, used, include, explicitExcluded);
|
|
80
|
+
const violations = packages.filter(pkg => explicitExcluded.includes(pkg.name)).flatMap(pkg => {
|
|
81
|
+
const reasons = [];
|
|
82
|
+
if (used.has(pkg.name))
|
|
83
|
+
reasons.push("used by the production bundle");
|
|
84
|
+
if (include.includes(pkg.name))
|
|
85
|
+
reasons.push("explicit native include");
|
|
86
|
+
for (const included of result.included) {
|
|
87
|
+
if (included.requires.includes(pkg.name))
|
|
88
|
+
reasons.push(`required by included ${included.name} (spark.requires)`);
|
|
89
|
+
if (Object.hasOwn(included.json.dependencies ?? {}, pkg.name) && !Object.hasOwn(included.json.optionalDependencies ?? {}, pkg.name))
|
|
90
|
+
reasons.push(`required by included ${included.name} (dependencies)`);
|
|
91
|
+
if (Object.hasOwn(included.json.peerDependencies ?? {}, pkg.name) && !included.json.peerDependenciesMeta?.[pkg.name]?.optional)
|
|
92
|
+
reasons.push(`required by included ${included.name} (peerDependencies)`);
|
|
93
|
+
}
|
|
94
|
+
if (!reasons.length)
|
|
95
|
+
return [];
|
|
96
|
+
const names = [pkg.name];
|
|
97
|
+
if (pkg.name.startsWith("@legendapp/spark-"))
|
|
98
|
+
names.push(`@legendapp/spark/${pkg.name.slice("@legendapp/spark-".length).replace(/^desktop-/, "")}`);
|
|
99
|
+
const importedFrom = map.sources.find(source => {
|
|
100
|
+
if (source.includes("/node_modules/") || source.startsWith("node_modules/"))
|
|
101
|
+
return false;
|
|
102
|
+
const file = sourceFile(source);
|
|
103
|
+
return file !== undefined && names.some(name => readFileSync(file, "utf8").includes(name));
|
|
104
|
+
});
|
|
105
|
+
return [{ name: pkg.name, reasons, ...(importedFrom ? { importedFrom } : {}) }];
|
|
106
|
+
});
|
|
72
107
|
writeJson(stateFile(root, "selection-report.json"), {
|
|
73
108
|
included: result.included.map((p) => p.name),
|
|
74
109
|
excluded: result.excluded.map((p) => p.name),
|
|
75
110
|
reasons: result.reasons,
|
|
76
111
|
sources: map.sources,
|
|
112
|
+
nativeCheck: { violations },
|
|
77
113
|
});
|
|
114
|
+
if (violations.length)
|
|
115
|
+
throw new Error([
|
|
116
|
+
`Release native-module check failed: ${violations.length} excluded native package${violations.length === 1 ? " is" : "s are"} required.`,
|
|
117
|
+
...violations.flatMap(violation => [
|
|
118
|
+
` ${violation.name}`,
|
|
119
|
+
...violation.reasons.map(reason => ` ${reason}${reason === "used by the production bundle" && violation.importedFrom ? ` (e.g. ${violation.importedFrom})` : ""}`),
|
|
120
|
+
" excluded by desktop.config.json expo.autolinking.exclude",
|
|
121
|
+
]),
|
|
122
|
+
"Fix: stop excluding it, or remove the import. In legend-apps, add the package (or its @legendapp/spark/<subpath>) to apps/<app>/app.manifest.ts nativeModules.macos.",
|
|
123
|
+
"Details: .spark/selection-report.json (nativeCheck)",
|
|
124
|
+
].join("\n"));
|
|
78
125
|
return result;
|
|
79
126
|
}
|
|
80
127
|
export async function build(root, mode, force = false) {
|
|
@@ -125,11 +172,10 @@ async function buildUnlocked(root, mode, force) {
|
|
|
125
172
|
throw new Error(`Build the Spark Runner from a generic SDK starter: ${issues.join("; ")}`);
|
|
126
173
|
}
|
|
127
174
|
await doctor(root);
|
|
128
|
-
const all = nativePackages(root);
|
|
129
175
|
const productionGraph = mode === "release" || mode === "preview";
|
|
130
176
|
const chosen = productionGraph
|
|
131
|
-
? await analyze(root
|
|
132
|
-
: { included:
|
|
177
|
+
? await analyze(root)
|
|
178
|
+
: { included: nativePackages(root), excluded: [] };
|
|
133
179
|
validateBuildModules(mode, chosen.included);
|
|
134
180
|
const runtime = runtimeFor(root, chosen.included, mode);
|
|
135
181
|
const productionHash = productionGraph
|
|
@@ -148,9 +194,9 @@ async function buildUnlocked(root, mode, force) {
|
|
|
148
194
|
});
|
|
149
195
|
const rnConfig = path.join(root, "react-native.config.js");
|
|
150
196
|
const marker = "// Generated by spark; extend with configuration plugins.";
|
|
151
|
-
if (existsSync(rnConfig) &&
|
|
197
|
+
if (!isUniversal(root) && (existsSync(rnConfig) &&
|
|
152
198
|
!readFileSync(rnConfig, "utf8").startsWith(marker) &&
|
|
153
|
-
!(isExpoProject(root) && readFileSync(rnConfig, "utf8").includes("// spark: existing Expo project")))
|
|
199
|
+
!(isExpoProject(root) && readFileSync(rnConfig, "utf8").includes("// spark: existing Expo project"))))
|
|
154
200
|
throw new Error("Existing react-native.config.js is not spark-managed. Compose the native selection explicitly before building.");
|
|
155
201
|
if (!isUniversal(root))
|
|
156
202
|
writeFileSync(rnConfig, `${marker}\nconst selection = require('./.spark/native-selection.json');\nmodule.exports = { dependencies: Object.fromEntries([\n...selection.included.map(p => [p.name, { root: p.root }]),\n...selection.excluded.map(name => [name, { platforms: { ios: null, macos: null, android: null } }])\n]) };\n`);
|
|
@@ -175,6 +221,7 @@ async function buildUnlocked(root, mode, force) {
|
|
|
175
221
|
if (!force && existing) {
|
|
176
222
|
if (existing.runtime.fingerprint === runtime.fingerprint &&
|
|
177
223
|
existing.preparation?.fingerprint === preparation.fingerprint &&
|
|
224
|
+
existing.product === path.basename(existing.app) &&
|
|
178
225
|
existsSync(existing.app)) {
|
|
179
226
|
console.log(`Reusing ${existing.app}`);
|
|
180
227
|
return existing;
|
|
@@ -184,6 +231,7 @@ async function buildUnlocked(root, mode, force) {
|
|
|
184
231
|
...preparationChanges(existing.preparation, preparation),
|
|
185
232
|
...(existing.runtime.fingerprint !== runtime.fingerprint ? ["runtime inputs changed"] : []),
|
|
186
233
|
...(!existsSync(existing.app) ? ["cached app missing"] : []),
|
|
234
|
+
...(existing.product !== path.basename(existing.app) ? ["cached app product is unverified"] : []),
|
|
187
235
|
];
|
|
188
236
|
console.log(`Building ${mode} runtime: ${reasons.join("; ")}.`);
|
|
189
237
|
const nativeConfig = readAppConfig(root).expo;
|
|
@@ -273,11 +321,12 @@ async function buildUnlocked(root, mode, force) {
|
|
|
273
321
|
label: `Building ${mode === "go" ? "Spark Runner" : mode} runtime (${configuration}, ${runtime.arch})`,
|
|
274
322
|
});
|
|
275
323
|
const products = path.join(derived, "Build", "Products", configuration);
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
324
|
+
// The scheme's product, not the first .app: a renamed app leaves its old bundle in DerivedData.
|
|
325
|
+
const product = `${name}.app`;
|
|
326
|
+
if (!existsSync(path.join(products, product)))
|
|
327
|
+
throw new Error(`Build completed without ${product}.`);
|
|
279
328
|
const destination = stateFile(root, `products/macos-${runtime.arch}/${mode}/${product}`);
|
|
280
|
-
rmSync(destination, { recursive: true, force: true });
|
|
329
|
+
rmSync(path.dirname(destination), { recursive: true, force: true });
|
|
281
330
|
mkdirSync(path.dirname(destination), { recursive: true });
|
|
282
331
|
cpSync(path.join(products, product), destination, { recursive: true, verbatimSymlinks: true });
|
|
283
332
|
if (mode === "release" &&
|
|
@@ -285,6 +334,8 @@ async function buildUnlocked(root, mode, force) {
|
|
|
285
334
|
throw new Error("Standalone build is missing its JavaScript bundle.");
|
|
286
335
|
const result = {
|
|
287
336
|
app: destination,
|
|
337
|
+
// Older receipts may record a stale bundle selected from DerivedData.
|
|
338
|
+
product,
|
|
288
339
|
runtime: runtimeFor(root, chosen.included, mode),
|
|
289
340
|
preparation,
|
|
290
341
|
};
|
|
@@ -6,7 +6,7 @@ const options = {
|
|
|
6
6
|
device: { type: "string" }, project: { type: "string" }, platform: { type: "string" },
|
|
7
7
|
packages: { type: "string" }, port: { type: "string" }, runner: { type: "boolean" },
|
|
8
8
|
dev: { type: "boolean" }, release: { type: "boolean" }, preview: { type: "boolean" },
|
|
9
|
-
force: { type: "boolean" }, "submission-id": { type: "string" }, help: { type: "boolean", short: "h" },
|
|
9
|
+
force: { type: "boolean" }, "dry-run": { type: "boolean" }, "submission-id": { type: "string" }, help: { type: "boolean", short: "h" },
|
|
10
10
|
};
|
|
11
11
|
const commands = {
|
|
12
12
|
create: { flags: ["example", "package-manager", "universal", "platform", "packages"], arguments: [1, 1] },
|
|
@@ -20,6 +20,7 @@ const commands = {
|
|
|
20
20
|
build: { flags: ["project", "platform", "device", "port", "runner", "dev", "release", "preview", "force"], arguments: [0, 0] },
|
|
21
21
|
prebuild: { flags: ["project", "platform"], arguments: [0, 0] },
|
|
22
22
|
doctor: { flags: ["project"], arguments: [0, 0] },
|
|
23
|
+
upgrade: { flags: ["project", "dry-run"], arguments: [0, 0] },
|
|
23
24
|
"updates init": { flags: ["project", "platform"], arguments: [1, 1] },
|
|
24
25
|
credentials: { flags: ["project", "platform"], arguments: [0, 0] },
|
|
25
26
|
package: { flags: ["project", "platform", "force", "submission-id"], arguments: [0, 0] },
|
|
@@ -3,7 +3,8 @@ import { checkExpoDesktopNode } from "./expo-node.js";
|
|
|
3
3
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createRequire } from "node:module";
|
|
6
|
-
import { stateFile } from "
|
|
6
|
+
import { statePath as stateFile } from "@legendapp/spark-desktop-config/config.cjs";
|
|
7
|
+
import { CommandError } from "./errors.js";
|
|
7
8
|
const running = new Map();
|
|
8
9
|
const secretFlags = new Set(["--password", "--apple-id-password", "--token", "--secret", "--api-key", "-P"]);
|
|
9
10
|
export function commandRedactor(argv, secrets = []) {
|
|
@@ -74,7 +75,7 @@ export async function run(root, argv, options = {}) {
|
|
|
74
75
|
process.stdout.write(redact(output));
|
|
75
76
|
}
|
|
76
77
|
if (code)
|
|
77
|
-
throw new
|
|
78
|
+
throw new CommandError(argv.map(redact), code, redact(output), log);
|
|
78
79
|
if (options.label)
|
|
79
80
|
console.log(`${options.label} finished (${((Date.now() - started) / 1000).toFixed(1)}s).`);
|
|
80
81
|
return output;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
|
+
import { creationManifest } from "./local.js";
|
|
1
2
|
import { installedRelease, materializeReleasePackages, packageSources, verifyMaterializedReleasePackages } from "./release.js";
|
|
2
3
|
import { packageManager, managerCommand, applyOverrides, localArchive } from "./package-manager.js";
|
|
3
4
|
import { packArchive } from "./pack-archive.js";
|
|
4
|
-
import { spawnProcess } from "./process.js";
|
|
5
|
+
import { nodeCommand, spawnProcess } from "./process.js";
|
|
5
6
|
import { checkExpoDesktopNode } from "./expo-node.js";
|
|
6
7
|
import { configureExample } from "./examples.js";
|
|
7
8
|
import { hostPlatform } from "./platform.js";
|
|
8
|
-
import { nodeCommand } from "./windows.js";
|
|
9
9
|
import { cpSync, existsSync, readFileSync, writeFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
10
10
|
import path from "node:path";
|
|
11
11
|
import os from "node:os";
|
|
12
|
-
import { readJson, writeJson } from "./
|
|
12
|
+
import { readJson, writeJson } from "./json.js";
|
|
13
13
|
import { run } from "./commands.js";
|
|
14
14
|
// Resolve relative to the installed CLI so packed consumers use the same starter.
|
|
15
15
|
const template = path.resolve(import.meta.dirname, "../templates/blank-typescript");
|
|
@@ -37,6 +37,9 @@ export async function refreshLocalPackages(root, manifest, selectedManager) {
|
|
|
37
37
|
upgradeManagedEntry(root);
|
|
38
38
|
}
|
|
39
39
|
export async function create(root, archiveManifest, platform = hostPlatform(), universal = false, example, selectedManager) {
|
|
40
|
+
if (!(universal || example) && !["macos", "windows"].includes(platform))
|
|
41
|
+
throw new Error("Use create --universal for mobile/web targets");
|
|
42
|
+
archiveManifest ??= creationManifest();
|
|
40
43
|
const manager = packageManager(process.cwd(), selectedManager);
|
|
41
44
|
await checkExpoDesktopNode(path.resolve(import.meta.dirname, ".."));
|
|
42
45
|
const variant = universal || example ? "universal" : platform === "macos" ? "blank-typescript" : "windows";
|
|
@@ -5,6 +5,7 @@ import { createInterface } from "node:readline/promises";
|
|
|
5
5
|
import { run } from "./commands.js";
|
|
6
6
|
import { readJson, stateFile, writeJson } from "./project.js";
|
|
7
7
|
import { listNotaryProfiles } from "./notary-profiles.js";
|
|
8
|
+
import { isNetworkError } from "./errors.js";
|
|
8
9
|
export function parseIdentities(output) {
|
|
9
10
|
return [...output.matchAll(/\b([A-Fa-f0-9]{40})\s+"(Developer ID Application: [^"\n]+ \(([A-Z0-9]{10})\))"/g)]
|
|
10
11
|
.map((match) => ({ hash: match[1].toUpperCase(), name: match[2], teamId: match[3] }));
|
|
@@ -27,6 +28,17 @@ async function ask(question) {
|
|
|
27
28
|
input.close();
|
|
28
29
|
}
|
|
29
30
|
}
|
|
31
|
+
async function choose(question, defaultChoice, count) {
|
|
32
|
+
let choice;
|
|
33
|
+
while (choice === undefined) {
|
|
34
|
+
const answer = Number(await ask(`${question} [${defaultChoice}]: `) || String(defaultChoice));
|
|
35
|
+
if (Number.isInteger(answer) && answer >= 1 && answer <= count)
|
|
36
|
+
choice = answer;
|
|
37
|
+
else
|
|
38
|
+
console.error(`Choose a number from 1 to ${count}.`);
|
|
39
|
+
}
|
|
40
|
+
return choice;
|
|
41
|
+
}
|
|
30
42
|
export async function credentials(root, reset = false, execute = run) {
|
|
31
43
|
const file = stateFile(root, "signing.json");
|
|
32
44
|
const saved = !reset && existsSync(file) ? readJson(file) : {};
|
|
@@ -41,9 +53,7 @@ export async function credentials(root, reset = false, execute = run) {
|
|
|
41
53
|
if (process.stdin.isTTY) {
|
|
42
54
|
console.log("Available Developer ID signing certificates:");
|
|
43
55
|
console.log(matches.map((item, index) => `${index + 1}. ${item.name} [${item.hash}]`).join("\n"));
|
|
44
|
-
const choice =
|
|
45
|
-
if (!Number.isInteger(choice) || choice < 1 || choice > matches.length)
|
|
46
|
-
throw new Error("Invalid signing identity selection.");
|
|
56
|
+
const choice = await choose("Signing certificate", defaultChoice, matches.length);
|
|
47
57
|
selected = matches[choice - 1];
|
|
48
58
|
}
|
|
49
59
|
else if (matches.length > 1) {
|
|
@@ -66,14 +76,14 @@ export async function credentials(root, reset = false, execute = run) {
|
|
|
66
76
|
console.log(`Available saved notarization Keychain profiles${keychain ? ` in ${keychain}` : ""}:`);
|
|
67
77
|
console.log(profiles.length ? profiles.map((profile, index) => `${index + 1}. ${profile}`).join("\n") : "No saved notarization profiles found.");
|
|
68
78
|
console.log(`${manualChoice}. Enter an existing profile name\n${createChoice}. Create a notarization Keychain profile`);
|
|
69
|
-
const choice =
|
|
79
|
+
const choice = await choose("Notarization profile", defaultProfileChoice, createChoice);
|
|
70
80
|
if (Number.isInteger(choice) && choice >= 1 && choice <= profiles.length) {
|
|
71
81
|
keychainProfile = profiles[choice - 1];
|
|
72
82
|
}
|
|
73
83
|
else if (choice === manualChoice) {
|
|
74
84
|
keychainProfile = await ask("Existing notarization Keychain profile: ");
|
|
75
85
|
if (!keychainProfile)
|
|
76
|
-
|
|
86
|
+
console.error("Enter an existing profile name or select the create option.");
|
|
77
87
|
}
|
|
78
88
|
else if (choice === createChoice) {
|
|
79
89
|
keychainProfile = await ask(`New notarization Keychain profile [${suggestedProfile}]: `) || suggestedProfile;
|
|
@@ -84,29 +94,34 @@ export async function credentials(root, reset = false, execute = run) {
|
|
|
84
94
|
if (await child.exited)
|
|
85
95
|
throw new Error("Notarization credential setup failed. Run spark credentials to retry.");
|
|
86
96
|
}
|
|
87
|
-
|
|
88
|
-
throw new Error("Invalid notarization setup selection.");
|
|
89
|
-
}
|
|
90
|
-
needsSelection = false;
|
|
91
|
-
}
|
|
92
|
-
result = { ...selected, keychainProfile, ...(keychain ? { keychain } : {}) };
|
|
93
|
-
// Validate the Keychain profile before native compilation or signing.
|
|
94
|
-
try {
|
|
95
|
-
await execute(root, ["xcrun", "notarytool", "history", ...notaryAuth(result), "--output-format", "json"], { capture: true });
|
|
96
|
-
validated = true;
|
|
97
|
+
needsSelection = !keychainProfile;
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
suggestedProfile = keychainProfile;
|
|
105
|
-
keychainProfile = undefined;
|
|
106
|
-
needsSelection = true;
|
|
99
|
+
if (keychainProfile) {
|
|
100
|
+
result = { ...selected, keychainProfile, ...(keychain ? { keychain } : {}) };
|
|
101
|
+
// Validate the Keychain profile before native compilation or signing.
|
|
102
|
+
try {
|
|
103
|
+
await execute(root, ["xcrun", "notarytool", "history", ...notaryAuth(result), "--output-format", "json"], { capture: true });
|
|
104
|
+
validated = true;
|
|
107
105
|
}
|
|
108
|
-
|
|
109
|
-
|
|
106
|
+
catch (error) {
|
|
107
|
+
if (error instanceof Error && error.message.includes("No Keychain password item found for profile:")) {
|
|
108
|
+
const message = `Notarization Keychain profile ${JSON.stringify(keychainProfile)} was not found${keychain ? ` in ${JSON.stringify(keychain)}` : ""}. A signing certificate name does not create a notarization profile.`;
|
|
109
|
+
if (!process.stdin.isTTY)
|
|
110
|
+
throw new Error(`${message} Run spark credentials interactively to select an existing profile or explicitly create one.`, { cause: error });
|
|
111
|
+
console.error(message);
|
|
112
|
+
suggestedProfile = keychainProfile;
|
|
113
|
+
keychainProfile = undefined;
|
|
114
|
+
needsSelection = true;
|
|
115
|
+
}
|
|
116
|
+
else if (process.stdin.isTTY && isNetworkError(error)) {
|
|
117
|
+
console.error("Could not reach Apple’s notarization service. Check your internet connection or VPN.");
|
|
118
|
+
const retry = await ask("Retry validation with the same profile? [Y/n]: ");
|
|
119
|
+
if (retry && !/^y(?:es)?$/i.test(retry))
|
|
120
|
+
throw error;
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
110
125
|
}
|
|
111
126
|
}
|
|
112
127
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ts from "typescript";
|
|
4
|
+
import { supportedPlatforms } from "@legendapp/spark-desktop-config/config.cjs";
|
|
5
|
+
const packages = ["react", "react-native", "react-native-macos", "expo"];
|
|
6
|
+
// create/refreshLocalPackages use this starter's tested compatibility matrix too.
|
|
7
|
+
const defaults = JSON.parse(readFileSync(new URL("../templates/blank-typescript/package.json", import.meta.url), "utf8"));
|
|
8
|
+
export const expectedDependencyPins = Object.fromEntries(packages.map(name => [name, defaults.dependencies[name]]));
|
|
9
|
+
export function dependencyPinIssues(pkg, platforms) {
|
|
10
|
+
const required = packages.filter(name => name !== "react-native-macos" || platforms.includes("macos"));
|
|
11
|
+
const issues = [];
|
|
12
|
+
function check(name, value, where) {
|
|
13
|
+
// npm and pnpm permit an override to reference a direct dependency's spec.
|
|
14
|
+
const resolved = ["overrides", "pnpm"].includes(where[0]) && typeof value === "string" && value.startsWith("$")
|
|
15
|
+
? pkg.dependencies?.[value.slice(1)] ?? pkg.devDependencies?.[value.slice(1)] : value;
|
|
16
|
+
if (typeof resolved === "string" && /^(catalog|workspace):/.test(resolved)) {
|
|
17
|
+
console.log(`Skipping dependency pin check for ${where.join(" → ")}: ${resolved} is managed by the catalog or workspace.`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (resolved !== expectedDependencyPins[name])
|
|
21
|
+
issues.push({ package: name, found: typeof value === "string" ? value : JSON.stringify(value), expected: expectedDependencyPins[name], where });
|
|
22
|
+
}
|
|
23
|
+
for (const name of required) {
|
|
24
|
+
let present = false;
|
|
25
|
+
for (const section of ["dependencies", "devDependencies"]) {
|
|
26
|
+
if (Object.hasOwn(pkg[section] ?? {}, name)) {
|
|
27
|
+
present = true;
|
|
28
|
+
check(name, pkg[section][name], [section, name]);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (!present)
|
|
32
|
+
issues.push({ package: name, found: "(missing)", expected: expectedDependencyPins[name], where: ["dependencies", name], missing: true });
|
|
33
|
+
}
|
|
34
|
+
function overrides(entries, where, parent) {
|
|
35
|
+
for (const [selector, value] of Object.entries(entries ?? {})) {
|
|
36
|
+
// Yarn paths/globs, version-qualified npm selectors and pnpm parent>child selectors.
|
|
37
|
+
// Comparators after @, whitespace or || belong to the version range.
|
|
38
|
+
const child = selector.split(/(?<![@\s|])>(?=[@a-zA-Z])/).at(-1);
|
|
39
|
+
const segments = child.split("/");
|
|
40
|
+
const name = selector === "." ? parent : segments.at(-2)?.startsWith("@") ? undefined : required.find(name => new RegExp(`^${name}(?:@[^/]*)?$`).test(segments.at(-1)));
|
|
41
|
+
const location = [...where, selector];
|
|
42
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
43
|
+
overrides(value, location, name);
|
|
44
|
+
else if (name)
|
|
45
|
+
check(name, value, location);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
overrides(pkg.overrides, ["overrides"]);
|
|
49
|
+
overrides(pkg.resolutions, ["resolutions"]);
|
|
50
|
+
overrides(pkg.pnpm?.overrides, ["pnpm", "overrides"]);
|
|
51
|
+
return issues;
|
|
52
|
+
}
|
|
53
|
+
export function printDependencyPins(issues) {
|
|
54
|
+
if (!issues.length) {
|
|
55
|
+
console.log("Dependency pins match Spark's expected versions.");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const rows = [["Package", "Found", "Expected", "Where"], ...issues.map(issue => [issue.package, issue.found, issue.expected, issue.where.join(" → ")])];
|
|
59
|
+
const widths = rows[0].map((_, column) => Math.max(...rows.map(row => row[column].length)));
|
|
60
|
+
for (const row of rows)
|
|
61
|
+
console.log(row.map((value, column) => value.padEnd(widths[column])).join(" ").trimEnd());
|
|
62
|
+
}
|
|
63
|
+
/** Edit only mismatched values and insert missing pins, preserving existing formatting. */
|
|
64
|
+
export function upgradeDependencyPins(root, dryRun = false) {
|
|
65
|
+
const file = path.join(root, "package.json");
|
|
66
|
+
let source = readFileSync(file, "utf8");
|
|
67
|
+
const pkg = JSON.parse(source);
|
|
68
|
+
const parsed = ts.parseJsonText(file, source);
|
|
69
|
+
const object = parsed.statements[0].expression;
|
|
70
|
+
function property(object, key) {
|
|
71
|
+
return object.properties.find(p => ts.isPropertyAssignment(p) && ts.isStringLiteral(p.name) && p.name.text === key);
|
|
72
|
+
}
|
|
73
|
+
function rejectDuplicates(node, where, nested = true) {
|
|
74
|
+
if (!node || !ts.isObjectLiteralExpression(node))
|
|
75
|
+
return;
|
|
76
|
+
const keys = new Set();
|
|
77
|
+
for (const entry of node.properties) {
|
|
78
|
+
if (!ts.isPropertyAssignment(entry) || !ts.isStringLiteral(entry.name))
|
|
79
|
+
continue;
|
|
80
|
+
const key = entry.name.text;
|
|
81
|
+
if (keys.has(key))
|
|
82
|
+
throw new Error(`package.json has duplicate key ${JSON.stringify(key)} in ${where.join(" → ") || "root"}. No files were changed.`);
|
|
83
|
+
keys.add(key);
|
|
84
|
+
if (nested)
|
|
85
|
+
rejectDuplicates(entry.initializer, [...where, key]);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
rejectDuplicates(object, [], false);
|
|
89
|
+
for (const section of ["dependencies", "devDependencies", "overrides", "resolutions"])
|
|
90
|
+
rejectDuplicates(property(object, section)?.initializer, [section]);
|
|
91
|
+
const pnpm = property(object, "pnpm")?.initializer;
|
|
92
|
+
rejectDuplicates(pnpm, ["pnpm"], false);
|
|
93
|
+
if (pnpm && ts.isObjectLiteralExpression(pnpm))
|
|
94
|
+
rejectDuplicates(property(pnpm, "overrides")?.initializer, ["pnpm", "overrides"]);
|
|
95
|
+
const issues = dependencyPinIssues(pkg, supportedPlatforms(root));
|
|
96
|
+
if (!issues.length || dryRun)
|
|
97
|
+
return issues;
|
|
98
|
+
const edits = [];
|
|
99
|
+
const missing = issues.filter(issue => issue.missing);
|
|
100
|
+
if (missing.length) {
|
|
101
|
+
if ("dependencies" in pkg && (!pkg.dependencies || typeof pkg.dependencies !== "object" || Array.isArray(pkg.dependencies)))
|
|
102
|
+
throw new Error("package.json dependencies must be an object. No files were changed.");
|
|
103
|
+
const indent = source.match(/^[\t ]+(?=")/m)?.[0] ?? " ";
|
|
104
|
+
const newline = source.includes("\r\n") ? "\r\n" : "\n";
|
|
105
|
+
const dependencies = property(object, "dependencies")?.initializer;
|
|
106
|
+
const target = dependencies ?? object;
|
|
107
|
+
const entries = dependencies ? missing.map(issue => `${JSON.stringify(issue.package)}: ${JSON.stringify(issue.expected)}`)
|
|
108
|
+
: [`"dependencies": ${JSON.stringify(Object.fromEntries(missing.map(issue => [issue.package, issue.expected])), null, indent)}`];
|
|
109
|
+
const last = target.properties.at(-1);
|
|
110
|
+
const baseIndent = source.slice(0, target.getStart(parsed)).match(/(?:^|\n)([\t ]*)[^\n]*$/)[1];
|
|
111
|
+
const multiline = !last || source.slice(target.getStart(parsed), target.end).includes("\n");
|
|
112
|
+
const separator = multiline ? newline + baseIndent + indent : " ";
|
|
113
|
+
const text = entries.map(entry => entry.replaceAll("\n", newline + baseIndent + indent)).join("," + separator);
|
|
114
|
+
edits.push(last ? { start: last.end, end: last.end, text: "," + separator + text }
|
|
115
|
+
: { start: target.getStart(parsed) + 1, end: target.end - 1, text: separator + text + newline + baseIndent });
|
|
116
|
+
}
|
|
117
|
+
for (const issue of issues) {
|
|
118
|
+
if (issue.missing)
|
|
119
|
+
continue;
|
|
120
|
+
// Fixing the referenced direct dependency also fixes npm/pnpm's $ref override.
|
|
121
|
+
if (["overrides", "pnpm"].includes(issue.where[0]) && issue.found.startsWith("$") && issues.some(direct => ["dependencies", "devDependencies"].includes(direct.where[0]) && direct.package === issue.found.slice(1) && direct.expected === issue.expected))
|
|
122
|
+
continue;
|
|
123
|
+
let node = object;
|
|
124
|
+
for (const key of issue.where)
|
|
125
|
+
node = property(node, key).initializer;
|
|
126
|
+
edits.push({ start: node.getStart(parsed), end: node.end, text: JSON.stringify(issue.expected) });
|
|
127
|
+
}
|
|
128
|
+
for (const edit of edits.sort((a, b) => b.start - a.start))
|
|
129
|
+
source = source.slice(0, edit.start) + edit.text + source.slice(edit.end);
|
|
130
|
+
writeFileSync(file, source);
|
|
131
|
+
return issues;
|
|
132
|
+
}
|
|
@@ -4,7 +4,7 @@ import { prepareConfig, selectTarget, supportedPlatforms } from "@legendapp/spar
|
|
|
4
4
|
import { dev } from "./dev.js";
|
|
5
5
|
import { findProject } from "./local.js";
|
|
6
6
|
import { hostPlatform } from "./platform.js";
|
|
7
|
-
import { nodeCommand } from "./
|
|
7
|
+
import { nodeCommand } from "./process.js";
|
|
8
8
|
// Consume only spark options. Expo validates its flags, aliases and values.
|
|
9
9
|
export function devArguments(args) {
|
|
10
10
|
const spark = {};
|
|
@@ -3,7 +3,7 @@ import { acquireRunner } from "./runner-install.js";
|
|
|
3
3
|
import { spawnProcess } from "./process.js";
|
|
4
4
|
import { windowsMetroPort, stopWindowsMetro } from "./windows-metro.js";
|
|
5
5
|
import { architecture, projectPlatform } from "./platform.js";
|
|
6
|
-
import { nodeCommand } from "./
|
|
6
|
+
import { nodeCommand } from "./process.js";
|
|
7
7
|
import { readAppConfig } from "./project.js";
|
|
8
8
|
import { existsSync, rmSync, watch } from "node:fs";
|
|
9
9
|
import path from "node:path";
|