@crumbsdk/react-native 0.0.1-rc.4 → 0.0.1-rc.5
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 +10 -0
- package/android/build.gradle +1 -0
- package/app.plugin.js +34 -4
- package/lib/module/build-release.js +19 -0
- package/lib/module/build-release.js.map +1 -0
- package/lib/module/index.js +3 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/build-release.d.ts +6 -0
- package/lib/typescript/src/build-release.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/metro.cjs +35 -0
- package/metro.d.cts +2 -0
- package/native/ios/VERSION +1 -1
- package/native/ios/packages/ios/Sources/CrumbCore/Crumb.swift +1 -1
- package/native/ios/packages/ios/Sources/CrumbCore/CrumbJavaScriptCrashStore.swift +8 -2
- package/native/ios/packages/ios/Sources/CrumbCore/CrumbSDKVersion.swift +1 -1
- package/native/ios/provenance.json +1 -1
- package/package.json +18 -4
- package/plugin/build-hooks.cjs +52 -0
- package/src/build-release.ts +30 -0
- package/src/index.ts +3 -1
- package/tools/cli.cjs +116 -0
- package/tools/config.cjs +72 -0
- package/tools/crumb.gradle +63 -0
- package/tools/expo-export.cjs +122 -0
- package/tools/ios.cjs +93 -0
- package/tools/node.cjs +21 -0
- package/tools/release.cjs +65 -0
- package/tools/setup.cjs +291 -0
- package/tools/upload.cjs +137 -0
package/README.md
CHANGED
|
@@ -4,6 +4,16 @@ The official Nitro Module adapter for Crumb. It is intentionally thin: the
|
|
|
4
4
|
published Swift and Kotlin SDKs continue to own reporter presentation,
|
|
5
5
|
screenshot masking, diagnostics, durable storage, and upload.
|
|
6
6
|
|
|
7
|
+
## Guided setup preview
|
|
8
|
+
|
|
9
|
+
The rc.5 candidate includes guided setup and automatic source-map uploads through
|
|
10
|
+
the React Native package. See the [React Native and Expo setup guide](../../docs/react-native-setup.md).
|
|
11
|
+
These commands are not available in the published rc.4 package yet. The existing
|
|
12
|
+
installation below remains available while the new candidate is validated.
|
|
13
|
+
See the [rc.5 release notes](../../docs/releases/0.0.1-rc.5.md) for exact candidate
|
|
14
|
+
installation and upgrade instructions. The uploader is included automatically;
|
|
15
|
+
its separate package version is an implementation dependency, not another setup step.
|
|
16
|
+
|
|
7
17
|
## Requirements
|
|
8
18
|
|
|
9
19
|
- React Native 0.79 or newer
|
package/android/build.gradle
CHANGED
package/app.plugin.js
CHANGED
|
@@ -1,21 +1,51 @@
|
|
|
1
1
|
const { configurePodfile } = require('./plugin/podfile.cjs');
|
|
2
2
|
const { name, version } = require('./package.json');
|
|
3
|
+
const {
|
|
4
|
+
configureXcodeProject,
|
|
5
|
+
configureGradle,
|
|
6
|
+
} = require('./plugin/build-hooks.cjs');
|
|
7
|
+
const { readConfig } = require('./tools/config.cjs');
|
|
3
8
|
|
|
4
9
|
module.exports = function withCrumb(config) {
|
|
5
10
|
// Resolve Expo from the consuming app, including non-hoisted workspaces.
|
|
6
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
withPodfile,
|
|
13
|
+
withXcodeProject,
|
|
14
|
+
withAppBuildGradle,
|
|
15
|
+
createRunOncePlugin,
|
|
16
|
+
} = require(
|
|
7
17
|
require.resolve('expo/config-plugins', {
|
|
8
18
|
paths: [config._internal?.projectRoot ?? process.cwd()],
|
|
9
19
|
})
|
|
10
20
|
);
|
|
11
21
|
return createRunOncePlugin(
|
|
12
|
-
(appConfig) =>
|
|
13
|
-
withPodfile(appConfig, (modConfig) => {
|
|
22
|
+
(appConfig) => {
|
|
23
|
+
appConfig = withPodfile(appConfig, (modConfig) => {
|
|
14
24
|
modConfig.modResults.contents = configurePodfile(
|
|
15
25
|
modConfig.modResults.contents
|
|
16
26
|
);
|
|
17
27
|
return modConfig;
|
|
18
|
-
})
|
|
28
|
+
});
|
|
29
|
+
if (
|
|
30
|
+
!readConfig(config._internal?.projectRoot ?? process.cwd()).sourceMaps
|
|
31
|
+
.enabled
|
|
32
|
+
)
|
|
33
|
+
return appConfig;
|
|
34
|
+
appConfig = withXcodeProject(appConfig, (modConfig) => {
|
|
35
|
+
configureXcodeProject(modConfig.modResults);
|
|
36
|
+
return modConfig;
|
|
37
|
+
});
|
|
38
|
+
return withAppBuildGradle(appConfig, (modConfig) => {
|
|
39
|
+
if (modConfig.modResults.language !== 'groovy')
|
|
40
|
+
throw new Error(
|
|
41
|
+
'Crumb automatic setup needs a Groovy app build file. Use the manual build-hook instructions for Kotlin DSL.'
|
|
42
|
+
);
|
|
43
|
+
modConfig.modResults.contents = configureGradle(
|
|
44
|
+
modConfig.modResults.contents
|
|
45
|
+
);
|
|
46
|
+
return modConfig;
|
|
47
|
+
});
|
|
48
|
+
},
|
|
19
49
|
name,
|
|
20
50
|
version
|
|
21
51
|
)(config);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export function withBuildRelease(configuration) {
|
|
4
|
+
const bundleVersion = globalThis.__CRUMB_BUNDLE_VERSION__;
|
|
5
|
+
if (typeof bundleVersion !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/.test(bundleVersion)) {
|
|
6
|
+
return configuration;
|
|
7
|
+
}
|
|
8
|
+
if (configuration.release?.bundleVersion !== undefined && configuration.release.bundleVersion !== bundleVersion) {
|
|
9
|
+
throw new TypeError('Crumb release.bundleVersion conflicts with the generated build identity. Remove the manual value or disable automatic source-map setup.');
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
...configuration,
|
|
13
|
+
release: {
|
|
14
|
+
...configuration.release,
|
|
15
|
+
bundleVersion
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=build-release.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["withBuildRelease","configuration","bundleVersion","globalThis","__CRUMB_BUNDLE_VERSION__","test","release","undefined","TypeError"],"sourceRoot":"../../src","sources":["build-release.ts"],"mappings":";;AAOA,OAAO,SAASA,gBAAgBA,CAC9BC,aAAiC,EACb;EACpB,MAAMC,aAAa,GAAGC,UAAU,CAACC,wBAAwB;EACzD,IACE,OAAOF,aAAa,KAAK,QAAQ,IACjC,CAAC,qCAAqC,CAACG,IAAI,CAACH,aAAa,CAAC,EAC1D;IACA,OAAOD,aAAa;EACtB;EACA,IACEA,aAAa,CAACK,OAAO,EAAEJ,aAAa,KAAKK,SAAS,IAClDN,aAAa,CAACK,OAAO,CAACJ,aAAa,KAAKA,aAAa,EACrD;IACA,MAAM,IAAIM,SAAS,CACjB,yIACF,CAAC;EACH;EACA,OAAO;IACL,GAAGP,aAAa;IAChBK,OAAO,EAAE;MAAE,GAAGL,aAAa,CAACK,OAAO;MAAEJ;IAAc;EACrD,CAAC;AACH","ignoreList":[]}
|
package/lib/module/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import NativeCrumbReactNative from './NativeCrumbReactNative';
|
|
4
|
+
import { withBuildRelease } from "./build-release.js";
|
|
4
5
|
import { currentScreenJson, setScreen, trackReactNavigation, useExpoRouterScreen } from "./screen-context.js";
|
|
5
6
|
export { setScreen, trackReactNavigation, useExpoRouterScreen };
|
|
6
7
|
import { configureJavaScriptCrashCapture, recoverJavaScriptCrashes } from "./javascript-crash-capture.js";
|
|
7
8
|
import { clearLogs, configureLogBuffer, disableConsoleCapture, enableConsoleCapture, markNativeStarted, writeLog } from "./log-buffer.js";
|
|
8
|
-
export async function start(
|
|
9
|
+
export async function start(input) {
|
|
10
|
+
const configuration = withBuildRelease(input);
|
|
9
11
|
validateConfiguration(configuration);
|
|
10
12
|
const logOptions = configuration.diagnostics?.logs;
|
|
11
13
|
configureLogBuffer(logOptions);
|
package/lib/module/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["NativeCrumbReactNative","currentScreenJson","setScreen","trackReactNavigation","useExpoRouterScreen","configureJavaScriptCrashCapture","recoverJavaScriptCrashes","clearLogs","configureLogBuffer","disableConsoleCapture","enableConsoleCapture","markNativeStarted","writeLog","start","configuration","validateConfiguration","logOptions","diagnostics","logs","JSON","stringify","captureConsole","javascriptCrashCapture","enabled","installReporter","show","log","level","message","metadata","trim","TypeError","projectKey","environment","assertPositiveInteger","capture","maximumScreenshotDimension","maximumScreenshotBytes","timeoutMs","lookbackMs","maximumEntries","maximumBytes","renderingEnabled","undefined","assertJavaScriptCrashCapture","reporter","theme","includes","assertReporterFields","visibleFields","assertEvidence","evidence","assertApplicationMetadata","application","name","assertCustomContext","customContext","assertWorkspacePolicy","workspacePolicy","value","maximumBreadcrumbs","maximumBreadcrumbBytes","Set","size","length","some","field","supported","item","hasControlCharacters","allowedKeys","values","Array","isArray","key","isValidContextKey","entries","Object","url","URL","protocol","username","password","search","hash","test","character","Number","isSafeInteger","Crumb","freeze"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,OAAOA,sBAAsB,MAAM,0BAA0B;AAC7D,SACEC,iBAAiB,EACjBC,SAAS,EACTC,oBAAoB,EACpBC,mBAAmB,QACd,qBAAkB;AACzB,SAASF,SAAS,EAAEC,oBAAoB,EAAEC,mBAAmB;AAE7D,SACEC,+BAA+B,EAC/BC,wBAAwB,QACnB,+BAA4B;AACnC,SACEC,SAAS,EACTC,kBAAkB,EAClBC,qBAAqB,EACrBC,oBAAoB,EACpBC,iBAAiB,EACjBC,QAAQ,QACH,iBAAc;AA6BrB,OAAO,eAAeC,KAAKA,CAACC,
|
|
1
|
+
{"version":3,"names":["NativeCrumbReactNative","withBuildRelease","currentScreenJson","setScreen","trackReactNavigation","useExpoRouterScreen","configureJavaScriptCrashCapture","recoverJavaScriptCrashes","clearLogs","configureLogBuffer","disableConsoleCapture","enableConsoleCapture","markNativeStarted","writeLog","start","input","configuration","validateConfiguration","logOptions","diagnostics","logs","JSON","stringify","captureConsole","javascriptCrashCapture","enabled","installReporter","show","log","level","message","metadata","trim","TypeError","projectKey","environment","assertPositiveInteger","capture","maximumScreenshotDimension","maximumScreenshotBytes","timeoutMs","lookbackMs","maximumEntries","maximumBytes","renderingEnabled","undefined","assertJavaScriptCrashCapture","reporter","theme","includes","assertReporterFields","visibleFields","assertEvidence","evidence","assertApplicationMetadata","application","name","assertCustomContext","customContext","assertWorkspacePolicy","workspacePolicy","value","maximumBreadcrumbs","maximumBreadcrumbBytes","Set","size","length","some","field","supported","item","hasControlCharacters","allowedKeys","values","Array","isArray","key","isValidContextKey","entries","Object","url","URL","protocol","username","password","search","hash","test","character","Number","isSafeInteger","Crumb","freeze"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-release.d.ts","sourceRoot":"","sources":["../../../src/build-release.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAS,CAAC;AAElD,OAAO,CAAC,MAAM,CAAC;IAEb,IAAI,wBAAwB,EAAE,OAAO,CAAC;CACvC;AAED,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,kBAAkB,GAChC,kBAAkB,CAoBpB"}
|
|
@@ -4,7 +4,7 @@ export type { CrumbScreenOptions, CrumbNavigationRef } from './screen-context.js
|
|
|
4
4
|
import { clearLogs, disableConsoleCapture, enableConsoleCapture } from './log-buffer.js';
|
|
5
5
|
import type { CrumbConfiguration, CrumbLogLevel, CrumbLogMetadata } from './types.js';
|
|
6
6
|
export type { CrumbApplicationMetadata, CrumbCaptureOptions, CrumbConfiguration, CrumbCustomContextOptions, CrumbDiagnosticsOptions, CrumbJavaScriptCrashCaptureOptions, CrumbEvidenceCategory, CrumbInvocation, CrumbLogLevel, CrumbLogMetadata, CrumbLogOptions, CrumbPrivacyOptions, CrumbReporterField, CrumbReporterOptions, CrumbRelease, CrumbTheme, CrumbUploadOptions, CrumbWorkspacePolicyOptions, } from './types.js';
|
|
7
|
-
export declare function start(
|
|
7
|
+
export declare function start(input: CrumbConfiguration): Promise<void>;
|
|
8
8
|
export declare function installReporter(): Promise<boolean>;
|
|
9
9
|
export declare function show(): Promise<boolean>;
|
|
10
10
|
export declare function log(level: CrumbLogLevel, message: string, metadata?: CrumbLogMetadata): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,SAAS,EACT,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,qBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;AAChE,YAAY,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,qBAAkB,CAAC;AAK/E,OAAO,EACL,SAAS,EAET,qBAAqB,EACrB,oBAAoB,EAGrB,MAAM,iBAAc,CAAC;AACtB,OAAO,KAAK,EACV,kBAAkB,EAElB,aAAa,EACb,gBAAgB,EACjB,MAAM,YAAS,CAAC;AAEjB,YAAY,EACV,wBAAwB,EACxB,mBAAmB,EACnB,kBAAkB,EAClB,yBAAyB,EACzB,uBAAuB,EACvB,kCAAkC,EAClC,qBAAqB,EACrB,eAAe,EACf,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,UAAU,EACV,kBAAkB,EAClB,2BAA2B,GAC5B,MAAM,YAAS,CAAC;AAEjB,wBAAsB,KAAK,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBpE;AAED,wBAAgB,eAAe,IAAI,OAAO,CAAC,OAAO,CAAC,CAElD;AAED,wBAAgB,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,CAEvC;AAED,wBAAgB,GAAG,CACjB,KAAK,EAAE,aAAa,EACpB,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,gBAAgB,GAC1B,IAAI,CAKN;AAED,OAAO,EAAE,SAAS,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,CAAC;AAoOlE,QAAA,MAAM,KAAK;;;;;;;;;;;EAWT,CAAC;AAEH,eAAe,KAAK,CAAC"}
|
package/metro.cjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const { readRelease } = require('./tools/release.cjs');
|
|
2
|
+
|
|
3
|
+
/** Preserve existing Metro options and include non-secret release metadata before app startup. */
|
|
4
|
+
function withCrumb(config) {
|
|
5
|
+
if (typeof config === 'function')
|
|
6
|
+
return (...args) => withCrumb(config(...args));
|
|
7
|
+
if (config && typeof config.then === 'function')
|
|
8
|
+
return config.then(withCrumb);
|
|
9
|
+
if (!config || typeof config !== 'object')
|
|
10
|
+
throw new Error('Pass your existing Metro configuration to withCrumb.');
|
|
11
|
+
const manifest = process.env.CRUMB_RELEASE_MANIFEST;
|
|
12
|
+
if (!manifest) return config;
|
|
13
|
+
const release = readRelease(manifest);
|
|
14
|
+
const getPolyfills = config.serializer?.getPolyfills;
|
|
15
|
+
return {
|
|
16
|
+
...config,
|
|
17
|
+
serializer: {
|
|
18
|
+
...config.serializer,
|
|
19
|
+
getPolyfills: (...args) => [
|
|
20
|
+
...new Set([
|
|
21
|
+
...(getPolyfills ? getPolyfills(...args) : []),
|
|
22
|
+
release.polyfill,
|
|
23
|
+
]),
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
watchFolders: [
|
|
27
|
+
...new Set([
|
|
28
|
+
...(config.watchFolders || []),
|
|
29
|
+
require('node:path').dirname(release.polyfill),
|
|
30
|
+
]),
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
module.exports = { withCrumb };
|
package/metro.d.cts
ADDED
package/native/ios/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.0.1-rc.
|
|
1
|
+
0.0.1-rc.5
|
|
@@ -24,7 +24,7 @@ public enum Crumb {
|
|
|
24
24
|
settings.diagnostics.javascriptCrashCaptureEnabled else {
|
|
25
25
|
return
|
|
26
26
|
}
|
|
27
|
-
_ = CrumbJavaScriptCrashStore.shared.record(recordJSON, failureContext: CrumbJavaScriptFailureContext.capture(settings: settings), includeBreadcrumbs: settings.diagnostics.logs.enabled && settings.evidence.contains(.logs))
|
|
27
|
+
_ = CrumbJavaScriptCrashStore.shared.record(recordJSON, failureContext: CrumbJavaScriptFailureContext.capture(settings: settings), includeBreadcrumbs: settings.diagnostics.logs.enabled && settings.evidence.contains(.logs), release: settings.release)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
/// Moves pending JavaScript failures into the regular durable report queue.
|
|
@@ -22,7 +22,7 @@ package struct CrumbJavaScriptCrash: Equatable, Sendable {
|
|
|
22
22
|
package let message: String
|
|
23
23
|
package let stack: String?
|
|
24
24
|
package let occurredAt: Date
|
|
25
|
-
package
|
|
25
|
+
package var release: CrumbJavaScriptCrashRelease
|
|
26
26
|
package var breadcrumbs: [CrumbJavaScriptBreadcrumb]
|
|
27
27
|
package let context: [String: String]
|
|
28
28
|
package let isFatal: Bool
|
|
@@ -80,7 +80,7 @@ package final class CrumbJavaScriptCrashStore: @unchecked Sendable {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
@discardableResult
|
|
83
|
-
package func record(_ recordJSON: String, failureContext: CrumbJavaScriptFailureContext? = nil, includeBreadcrumbs: Bool = true) -> Bool {
|
|
83
|
+
package func record(_ recordJSON: String, failureContext: CrumbJavaScriptFailureContext? = nil, includeBreadcrumbs: Bool = true, release: CrumbRelease? = nil) -> Bool {
|
|
84
84
|
lock.lock()
|
|
85
85
|
defer { lock.unlock() }
|
|
86
86
|
|
|
@@ -94,6 +94,12 @@ package final class CrumbJavaScriptCrashStore: @unchecked Sendable {
|
|
|
94
94
|
|
|
95
95
|
// The JS payload cannot supply native context. Only the trusted capture argument can.
|
|
96
96
|
record.failureContext = failureContext?.validated()
|
|
97
|
+
// Freeze native defaults at failure time, before a later app update can change them.
|
|
98
|
+
record.release = CrumbJavaScriptCrashRelease(
|
|
99
|
+
appVersion: record.release.appVersion ?? release?.appVersion,
|
|
100
|
+
nativeBuild: record.release.nativeBuild ?? release?.nativeBuild,
|
|
101
|
+
bundleVersion: record.release.bundleVersion ?? release?.bundleVersion
|
|
102
|
+
)
|
|
97
103
|
if !includeBreadcrumbs { record.breadcrumbs = [] }
|
|
98
104
|
|
|
99
105
|
do {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@crumbsdk/react-native",
|
|
3
|
-
"version": "0.0.1-rc.
|
|
3
|
+
"version": "0.0.1-rc.5",
|
|
4
4
|
"description": "React Native adapter for the native Crumb issue-reporting SDK",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -11,7 +11,11 @@
|
|
|
11
11
|
"default": "./lib/module/index.js"
|
|
12
12
|
},
|
|
13
13
|
"./package.json": "./package.json",
|
|
14
|
-
"./app.plugin.js": "./app.plugin.js"
|
|
14
|
+
"./app.plugin.js": "./app.plugin.js",
|
|
15
|
+
"./metro": {
|
|
16
|
+
"types": "./metro.d.cts",
|
|
17
|
+
"default": "./metro.cjs"
|
|
18
|
+
}
|
|
15
19
|
},
|
|
16
20
|
"files": [
|
|
17
21
|
"src",
|
|
@@ -31,7 +35,10 @@
|
|
|
31
35
|
"native",
|
|
32
36
|
"scripts/ios.rb",
|
|
33
37
|
"app.plugin.js",
|
|
34
|
-
"plugin"
|
|
38
|
+
"plugin",
|
|
39
|
+
"metro.cjs",
|
|
40
|
+
"tools",
|
|
41
|
+
"metro.d.cts"
|
|
35
42
|
],
|
|
36
43
|
"scripts": {
|
|
37
44
|
"example": "yarn workspace @crumbsdk/react-native-example",
|
|
@@ -72,7 +79,7 @@
|
|
|
72
79
|
"access": "public",
|
|
73
80
|
"registry": "https://registry.npmjs.org/"
|
|
74
81
|
},
|
|
75
|
-
"crumbNativeVersion": "0.0.1-rc.
|
|
82
|
+
"crumbNativeVersion": "0.0.1-rc.5",
|
|
76
83
|
"devDependencies": {
|
|
77
84
|
"@babel/core": "7.29.7",
|
|
78
85
|
"@eslint/compat": "2.1.0",
|
|
@@ -144,5 +151,12 @@
|
|
|
144
151
|
"jest"
|
|
145
152
|
],
|
|
146
153
|
"version": "0.63.0"
|
|
154
|
+
},
|
|
155
|
+
"dependencies": {
|
|
156
|
+
"@crumbsdk/source-maps": "0.0.1-rc.4",
|
|
157
|
+
"xcode": "3.0.1"
|
|
158
|
+
},
|
|
159
|
+
"bin": {
|
|
160
|
+
"crumb": "tools/cli.cjs"
|
|
147
161
|
}
|
|
148
162
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const marker = 'crumb-release-build';
|
|
2
|
+
|
|
3
|
+
function shellQuote(value) {
|
|
4
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function configureXcodeProject(project) {
|
|
8
|
+
let found = false;
|
|
9
|
+
for (const phase of Object.values(
|
|
10
|
+
project.hash.project.objects.PBXShellScriptBuildPhase || {}
|
|
11
|
+
)) {
|
|
12
|
+
if (!phase || typeof phase.shellScript !== 'string') continue;
|
|
13
|
+
// Expo mods can supply literal control characters inside the quoted PBX value.
|
|
14
|
+
const original = phase.shellScript.startsWith('"')
|
|
15
|
+
? JSON.parse(
|
|
16
|
+
phase.shellScript
|
|
17
|
+
.replaceAll('\n', '\\n')
|
|
18
|
+
.replaceAll('\r', '\\r')
|
|
19
|
+
.replaceAll('\t', '\\t')
|
|
20
|
+
)
|
|
21
|
+
: phase.shellScript;
|
|
22
|
+
if (original.includes(marker)) {
|
|
23
|
+
found = true;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (!original.includes('react-native-xcode.sh')) continue;
|
|
27
|
+
const wrapper = `# ${marker}
|
|
28
|
+
set -e
|
|
29
|
+
if [ -f "$PODS_ROOT/../.xcode.env" ]; then . "$PODS_ROOT/../.xcode.env"; fi
|
|
30
|
+
if [ -f "$PODS_ROOT/../.xcode.env.local" ]; then . "$PODS_ROOT/../.xcode.env.local"; fi
|
|
31
|
+
NODE_BINARY="\${NODE_BINARY:-node}"
|
|
32
|
+
CRUMB_PACKAGE_DIR="$("$NODE_BINARY" --print "require('path').dirname(require.resolve('@crumbsdk/react-native/package.json'))")"
|
|
33
|
+
"$NODE_BINARY" "$CRUMB_PACKAGE_DIR/tools/ios.cjs" /bin/bash -c ${shellQuote(original)}
|
|
34
|
+
`;
|
|
35
|
+
phase.shellScript = JSON.stringify(wrapper);
|
|
36
|
+
// The original native script and the wrapper own their incremental outputs.
|
|
37
|
+
phase.alwaysOutOfDate = 1;
|
|
38
|
+
found = true;
|
|
39
|
+
}
|
|
40
|
+
if (!found)
|
|
41
|
+
throw new Error(
|
|
42
|
+
'Crumb could not locate the React Native Xcode bundle phase. See the manual build-hook instructions.'
|
|
43
|
+
);
|
|
44
|
+
return project;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function configureGradle(contents) {
|
|
48
|
+
if (contents.includes(marker)) return contents;
|
|
49
|
+
return `${contents}\n// ${marker}\napply from: new File(["node", "--print", "require.resolve('@crumbsdk/react-native/package.json')"].execute(null, rootDir).text.trim()).parentFile.toPath().resolve("tools/crumb.gradle").toFile()\n`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { configureXcodeProject, configureGradle };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CrumbConfiguration } from './types';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
// Written by the release build polyfill before application modules execute.
|
|
5
|
+
var __CRUMB_BUNDLE_VERSION__: unknown;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function withBuildRelease(
|
|
9
|
+
configuration: CrumbConfiguration
|
|
10
|
+
): CrumbConfiguration {
|
|
11
|
+
const bundleVersion = globalThis.__CRUMB_BUNDLE_VERSION__;
|
|
12
|
+
if (
|
|
13
|
+
typeof bundleVersion !== 'string' ||
|
|
14
|
+
!/^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$/.test(bundleVersion)
|
|
15
|
+
) {
|
|
16
|
+
return configuration;
|
|
17
|
+
}
|
|
18
|
+
if (
|
|
19
|
+
configuration.release?.bundleVersion !== undefined &&
|
|
20
|
+
configuration.release.bundleVersion !== bundleVersion
|
|
21
|
+
) {
|
|
22
|
+
throw new TypeError(
|
|
23
|
+
'Crumb release.bundleVersion conflicts with the generated build identity. Remove the manual value or disable automatic source-map setup.'
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
...configuration,
|
|
28
|
+
release: { ...configuration.release, bundleVersion },
|
|
29
|
+
};
|
|
30
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import NativeCrumbReactNative from './NativeCrumbReactNative';
|
|
2
|
+
import { withBuildRelease } from './build-release';
|
|
2
3
|
import {
|
|
3
4
|
currentScreenJson,
|
|
4
5
|
setScreen,
|
|
@@ -47,7 +48,8 @@ export type {
|
|
|
47
48
|
CrumbWorkspacePolicyOptions,
|
|
48
49
|
} from './types';
|
|
49
50
|
|
|
50
|
-
export async function start(
|
|
51
|
+
export async function start(input: CrumbConfiguration): Promise<void> {
|
|
52
|
+
const configuration = withBuildRelease(input);
|
|
51
53
|
validateConfiguration(configuration);
|
|
52
54
|
const logOptions = configuration.diagnostics?.logs;
|
|
53
55
|
configureLogBuffer(logOptions);
|
package/tools/cli.cjs
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const fs = require('node:fs');
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { parseArgs } = require('node:util');
|
|
5
|
+
const { createInterface } = require('node:readline/promises');
|
|
6
|
+
const { setupPlan, applyPlan, doctor } = require('./setup.cjs');
|
|
7
|
+
const { prepareRelease } = require('./release.cjs');
|
|
8
|
+
const { uploadRelease, retryUpload } = require('./upload.cjs');
|
|
9
|
+
|
|
10
|
+
async function main() {
|
|
11
|
+
if (Number(process.versions.node.split('.')[0]) < 22)
|
|
12
|
+
throw new Error('Crumb build tooling requires Node.js 22 or newer.');
|
|
13
|
+
const { positionals, values } = parseArgs({
|
|
14
|
+
allowPositionals: true,
|
|
15
|
+
options: {
|
|
16
|
+
help: { type: 'boolean' },
|
|
17
|
+
apply: { type: 'boolean' },
|
|
18
|
+
'source-maps': { type: 'boolean' },
|
|
19
|
+
'upload-url': { type: 'string' },
|
|
20
|
+
root: { type: 'string' },
|
|
21
|
+
directory: { type: 'string' },
|
|
22
|
+
input: { type: 'string' },
|
|
23
|
+
receipt: { type: 'string' },
|
|
24
|
+
output: { type: 'string' },
|
|
25
|
+
targets: { type: 'string' },
|
|
26
|
+
'dry-run': { type: 'boolean' },
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
const root = path.resolve(values.root || process.cwd());
|
|
30
|
+
const [command] = positionals;
|
|
31
|
+
if (values.help || !command) {
|
|
32
|
+
console.log(
|
|
33
|
+
'crumb setup [--source-maps --upload-url HTTPS_ORIGIN] [--apply]\ncrumb doctor\ncrumb retry --receipt FILE\ncrumb export --output DIRECTORY --targets FILE [--dry-run]\n\nSetup previews changes; --apply accepts them without prompting. Upload credentials belong only in CRUMB_SOURCE_MAP_TOKEN.'
|
|
34
|
+
);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (command === 'setup') {
|
|
38
|
+
let sourceMaps = values['source-maps'];
|
|
39
|
+
let uploadUrl = values['upload-url'];
|
|
40
|
+
const prompt =
|
|
41
|
+
process.stdin.isTTY && !values.apply
|
|
42
|
+
? createInterface({ input: process.stdin, output: process.stdout })
|
|
43
|
+
: undefined;
|
|
44
|
+
try {
|
|
45
|
+
if (prompt && sourceMaps === undefined)
|
|
46
|
+
sourceMaps = /^y(es)?$/i.test(
|
|
47
|
+
await prompt.question(
|
|
48
|
+
'Configure source maps for opt-in JavaScript crashes? [y/N] '
|
|
49
|
+
)
|
|
50
|
+
);
|
|
51
|
+
if (prompt && sourceMaps && !uploadUrl)
|
|
52
|
+
uploadUrl = await prompt.question(
|
|
53
|
+
'Upload origin from your Crumb integration page: '
|
|
54
|
+
);
|
|
55
|
+
const plan = setupPlan(root, { sourceMaps, uploadUrl });
|
|
56
|
+
console.log(`Crumb setup: ${plan.framework}`);
|
|
57
|
+
for (const change of plan.changes)
|
|
58
|
+
console.log(
|
|
59
|
+
`- ${change.before === undefined ? 'Create' : 'Update'} ${change.name}`
|
|
60
|
+
);
|
|
61
|
+
console.log(
|
|
62
|
+
'Setup adds Crumb configuration, preserves existing settings, and wraps supported build hooks. Review the resulting diff before committing.'
|
|
63
|
+
);
|
|
64
|
+
if (
|
|
65
|
+
values.apply ||
|
|
66
|
+
(prompt &&
|
|
67
|
+
/^y(es)?$/i.test(
|
|
68
|
+
await prompt.question(
|
|
69
|
+
`Apply ${plan.changes.length} reviewed file changes? [y/N] `
|
|
70
|
+
)
|
|
71
|
+
))
|
|
72
|
+
) {
|
|
73
|
+
applyPlan(plan);
|
|
74
|
+
console.log('Setup changes applied.');
|
|
75
|
+
} else
|
|
76
|
+
console.log(
|
|
77
|
+
'Preview only. Run again with --apply to apply these changes.'
|
|
78
|
+
);
|
|
79
|
+
for (const instruction of plan.instructions)
|
|
80
|
+
console.log(`- ${instruction}`);
|
|
81
|
+
} finally {
|
|
82
|
+
prompt?.close();
|
|
83
|
+
}
|
|
84
|
+
} else if (command === 'doctor') {
|
|
85
|
+
const result = doctor(root);
|
|
86
|
+
console.log(JSON.stringify(result, null, 2));
|
|
87
|
+
if (result.checks.some((check) => check.status === 'missing'))
|
|
88
|
+
process.exitCode = 1;
|
|
89
|
+
} else if (command === 'prepare' && values.directory) {
|
|
90
|
+
prepareRelease(path.resolve(values.directory));
|
|
91
|
+
} else if (command === 'upload-build' && values.input) {
|
|
92
|
+
uploadRelease(JSON.parse(fs.readFileSync(values.input, 'utf8')), {
|
|
93
|
+
dryRun: values['dry-run'],
|
|
94
|
+
});
|
|
95
|
+
} else if (command === 'export' && values.output && values.targets) {
|
|
96
|
+
require('./expo-export.cjs').exportRelease(
|
|
97
|
+
root,
|
|
98
|
+
values.output,
|
|
99
|
+
values.targets,
|
|
100
|
+
{ dryRun: values['dry-run'] }
|
|
101
|
+
);
|
|
102
|
+
} else if (command === 'retry' && values.receipt) {
|
|
103
|
+
retryUpload(values.receipt);
|
|
104
|
+
} else
|
|
105
|
+
throw new Error('Unknown or incomplete Crumb command. Run crumb --help.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
main().catch((error) => {
|
|
109
|
+
const message =
|
|
110
|
+
error instanceof SyntaxError ||
|
|
111
|
+
String(error.code).startsWith('ERR_PARSE_ARGS')
|
|
112
|
+
? 'Invalid command or JSON input. Run crumb --help; keep credentials only in the build environment.'
|
|
113
|
+
: error.message;
|
|
114
|
+
console.error(`Crumb: ${message}`);
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
});
|
package/tools/config.cjs
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
function readConfig(root) {
|
|
5
|
+
const file = path.join(root, 'crumb.config.json');
|
|
6
|
+
if (!fs.existsSync(file))
|
|
7
|
+
return { version: 1, sourceMaps: { enabled: false } };
|
|
8
|
+
let config;
|
|
9
|
+
try {
|
|
10
|
+
config = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
11
|
+
} catch {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'Could not read crumb.config.json. Check that it contains valid JSON without credentials.'
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
if (
|
|
17
|
+
!config ||
|
|
18
|
+
typeof config !== 'object' ||
|
|
19
|
+
Array.isArray(config) ||
|
|
20
|
+
config.version !== 1 ||
|
|
21
|
+
!config.sourceMaps ||
|
|
22
|
+
typeof config.sourceMaps !== 'object' ||
|
|
23
|
+
Array.isArray(config.sourceMaps) ||
|
|
24
|
+
Object.keys(config).some(
|
|
25
|
+
(key) => !['version', 'sourceMaps'].includes(key)
|
|
26
|
+
) ||
|
|
27
|
+
Object.keys(config.sourceMaps).some(
|
|
28
|
+
(key) => !['enabled', 'uploadUrl', 'strict'].includes(key)
|
|
29
|
+
) ||
|
|
30
|
+
typeof config.sourceMaps.enabled !== 'boolean' ||
|
|
31
|
+
(config.sourceMaps.strict !== undefined &&
|
|
32
|
+
typeof config.sourceMaps.strict !== 'boolean')
|
|
33
|
+
) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
'Invalid crumb.config.json. Use crumb setup; keep credentials only in the build environment.'
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (config.sourceMaps.enabled) validateOrigin(config.sourceMaps.uploadUrl);
|
|
39
|
+
return config;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function validateOrigin(value) {
|
|
43
|
+
let url;
|
|
44
|
+
try {
|
|
45
|
+
url = new URL(value);
|
|
46
|
+
} catch {
|
|
47
|
+
throw new Error(
|
|
48
|
+
'Supply the HTTPS upload origin shown in your Crumb integration instructions.'
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (
|
|
52
|
+
url.protocol !== 'https:' ||
|
|
53
|
+
url.username ||
|
|
54
|
+
url.password ||
|
|
55
|
+
url.search ||
|
|
56
|
+
url.hash ||
|
|
57
|
+
url.pathname !== '/'
|
|
58
|
+
) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'The upload origin must use HTTPS without a path, credentials, query, or fragment.'
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
return url.origin;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function isStrict(config, env = process.env) {
|
|
67
|
+
return (
|
|
68
|
+
env.CRUMB_SOURCE_MAP_STRICT === '1' || config.sourceMaps.strict === true
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = { readConfig, validateOrigin, isStrict };
|