13 Commits

Author SHA1 Message Date
7d2f048932 feat(mobile): shadcn like and tailwind added to make things look yummy
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m21s
2026-04-27 21:23:40 -05:00
649ae1ee9f feat(mobile): new route for the ehs launcher 2026-04-27 21:22:59 -05:00
8446dbc955 feat(servers): added iowa ebm
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 1m45s
2026-04-26 19:51:49 -05:00
0b7318f856 fix(mobile): typo for version checking 2026-04-26 19:51:12 -05:00
bddc9aca0d refactor(mobile): moved the versioning lookup at at the mobile folder plus renamed 2026-04-26 18:33:28 -05:00
77b4533dea feat(scanner): more work on the scanner and can now scan to prod no lst right now
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m41s
2026-04-25 18:13:07 -05:00
83a542d1b7 build(scripts): changing how the relase works so it purposly builds before it trys to release
this is to help prevent errors in the build and release stuff in git
2026-04-23 07:48:13 -05:00
4855412733 chore(release): 0.0.2-alpha.6
All checks were successful
Build and Push LST Docker Image / docker (push) Successful in 2m20s
Release and Build Image / release (push) Successful in 14s
2026-04-23 07:24:45 -05:00
251970ec8f chore(release): 0.0.2-alpha.1
All checks were successful
Release and Build Image / release (push) Successful in 2m36s
2026-04-23 07:24:12 -05:00
f7ea5f709e chore(release): 0.0.2-alpha.0
All checks were successful
Release and Build Image / release (push) Successful in 2m51s
2026-04-23 07:24:02 -05:00
3d3c2aa964 chore(release): 0.0.1
All checks were successful
Release and Build Image / release (push) Successful in 2m39s
2026-04-23 07:23:49 -05:00
781025dca0 fix(frontend): lingering import crashed us 2026-04-23 07:23:25 -05:00
a593bb2baa chore(doc remove): removed a doc and put it in the real area for docs 2026-04-23 07:23:01 -05:00
48 changed files with 2508 additions and 432 deletions

View File

@@ -1,5 +1,23 @@
# All Changes to LST can be found below.
## [0.0.2-alpha.6](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.1...v0.0.2-alpha.6) (2026-04-23)
## [0.0.2-alpha.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.2-alpha.0...v0.0.2-alpha.1) (2026-04-23)
## [0.0.2-alpha.0](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1...v0.0.2-alpha.0) (2026-04-23)
## [0.0.1](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.5...v0.0.1) (2026-04-23)
### 🐛 Bug fixes
* **frontend:** lingering import crashed us ([781025d](https://git.tuffraid.net/cowch/lst_v3/commits/781025dca00e9dd4b2ad9b283be944ed91bbc1e5))
### 📝 Chore
* **doc remove:** removed a doc and put it in the real area for docs ([a593bb2](https://git.tuffraid.net/cowch/lst_v3/commits/a593bb2baafd0166a178b80cd76dd8862f240e11))
## [0.0.1-alpha.5](https://git.tuffraid.net/cowch/lst_v3/compare/v0.0.1-alpha.4...v0.0.1-alpha.5) (2026-04-23)

View File

@@ -129,6 +129,17 @@ const servers: NewServerData[] = [
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
{
name: "Iowa City EBM",
server: "USIOW1VMS006",
plantToken: "usiow1",
idAddress: "10.75.0.26",
greatPlainsPlantCode: "30",
contactEmail: "",
contactPhone: "",
serverLoc: "D$\\LST_V3",
buildNumber: 1,
},
];
// notes here for when it comes time to pull in all the server address info [test1_AlplaPROD2.0_Read].[masterData].[Plant] has everything from every server :D

View File

@@ -9,12 +9,19 @@ const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const downloadDir = path.resolve(__dirname, "../../downloads/mobile");
const projectRoot = path.resolve("./lstMobile"); // adjust as needed
const appJsonPath = path.join(projectRoot, "app.json");
const raw = fs.readFileSync(appJsonPath, "utf-8");
const config = JSON.parse(raw);
const exp = config.expo;
const currentApk = {
packageName: "net.alpla.lst.mobile",
versionName: "0.0.1-alpha",
versionCode: 1,
minSupportedVersionCode: 1,
packageName: exp.android?.package,
versionName: exp.version,
versionCode: exp.android?.versionCode,
minSupportedVersionCode: 1, // keep this custom if needed
fileName: "lst-mobile.apk",
};
@@ -46,4 +53,17 @@ router.get("/apk/latest", (_, res) => {
return res.sendFile(apkPath);
});
router.get("/apk/ehs", (_, res) => {
const apkPath = path.join(downloadDir, "EHS.apk");
if (!fs.existsSync(apkPath)) {
return res.status(404).json({ success: false, message: "APK not found" });
}
res.setHeader("Content-Type", "application/vnd.android.package-archive");
res.setHeader("Content-Disposition", `attachment; filename="EHS.apk}"`);
return res.sendFile(apkPath);
});
export default router;

View File

@@ -1,5 +1,5 @@
import { Link, useNavigate } from "@tanstack/react-router";
import { Cat, LogIn } from "lucide-react";
import { Cat } from "lucide-react";
import { toast } from "sonner";
import {
Card,

View File

@@ -2,9 +2,9 @@
"expo": {
"name": "LST mobile",
"slug": "lst-mobile",
"version": "0.0.1-alpha",
"version": "0.11.1-alpha",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"icon": "./assets/icon_white.png",
"scheme": "lstmobile",
"userInterfaceStyle": "automatic",
"ios": {
@@ -12,29 +12,35 @@
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"backgroundImage": "./assets/images/android-icon-background.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png",
"package": "net.alpla.lst.mobile",
"versionCode": 1
"foregroundImage": "./assets/adaptive-icon-white.png",
"backgroundColor": "#ffffff"
},
"versionCode": 8,
"minSupportedVersionCode": 4,
"predictiveBackGestureEnabled": false,
"package": "com.anonymous.lstMobile"
"package": "net.alpla.lst.mobile"
},
"web": {
"output": "static",
"favicon": "./assets/images/favicon.png"
"favicon": "./assets/images/favicon.png",
"bundler": "metro"
},
"plugins": [
"./plugins/withZebraScanner",
"expo-router",
[
"expo-splash-screen",
{
"backgroundColor": "#208AEF",
"android": {
"image": "./assets/images/splash-icon.png",
"imageWidth": 76
"resizeMode": "cover",
"image": "./assets/splash_white.png",
"backgroundColor": "#ffffff",
"dark": {
"image": "./assets/splash.png",
"backgroundColor": "#000000"
},
"imageWidth": 200
}
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
lstMobile/assets/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
lstMobile/assets/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,9 @@
module.exports = (api) => {
api.cache(true);
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
};
};

19
lstMobile/components.json Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "tailwind.config.js",
"css": "global.css",
"baseColor": "neutral",
"cssVariables": true
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
}
}

58
lstMobile/global.css Normal file
View File

@@ -0,0 +1,58 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 0 0% 3.9%;
--card: 0 0% 100%;
--card-foreground: 0 0% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 0 0% 3.9%;
--primary: 0 0% 9%;
--primary-foreground: 0 0% 98%;
--secondary: 0 0% 96.1%;
--secondary-foreground: 0 0% 9%;
--muted: 0 0% 96.1%;
--muted-foreground: 0 0% 45.1%;
--accent: 0 0% 96.1%;
--accent-foreground: 0 0% 9%;
--destructive: 0 84.2% 60.2%;
--border: 0 0% 89.8%;
--input: 0 0% 89.8%;
--ring: 0 0% 63%;
--radius: 0.625rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark:root {
--background: 0 0% 3.9%;
--foreground: 0 0% 98%;
--card: 0 0% 3.9%;
--card-foreground: 0 0% 98%;
--popover: 0 0% 3.9%;
--popover-foreground: 0 0% 98%;
--primary: 0 0% 98%;
--primary-foreground: 0 0% 9%;
--secondary: 0 0% 14.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 14.9%;
--muted-foreground: 0 0% 63.9%;
--accent: 0 0% 14.9%;
--accent-foreground: 0 0% 98%;
--destructive: 0 70.9% 59.4%;
--border: 0 0% 14.9%;
--input: 0 0% 14.9%;
--ring: 300 0% 45%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

View File

@@ -0,0 +1,9 @@
const { getDefaultConfig } = require("expo/metro-config");
const { withNativeWind } = require("nativewind/metro");
const config = getDefaultConfig(__dirname);
module.exports = withNativeWind(config, {
input: "./global.css",
inlineRem: 16,
});

3
lstMobile/nativewind-env.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
/// <reference types="nativewind/types" />
// NOTE: This file should not be edited and should be committed with your source code. It is generated by NativeWind.

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "lstmobile",
"main": "expo-router/entry",
"version": "0.0.1-alpha",
"version": "0.0.2-alpha",
"scripts": {
"start": "expo start",
"reset-project": "node ./scripts/reset-project.js",
@@ -9,7 +9,8 @@
"ios": "expo run:ios",
"web": "expo start --web",
"lint": "expo lint",
"build:apk": "expo prebuild --clean && cd android && gradlew.bat assembleRelease ",
"build:apk:clean": "expo prebuild --clean && cd android && gradlew.bat assembleRelease ",
"build:apk": "expo prebuild && cd android && gradlew.bat assembleRelease && cd .. && copy /Y android\\app\\build\\outputs\\apk\\release\\app-release.apk downloads\\mobile\\lst-mobile.apk",
"update": "adb install android/app/build/outputs/apk/release/app-release.apk"
},
"dependencies": {
@@ -17,8 +18,13 @@
"@react-navigation/bottom-tabs": "^7.15.5",
"@react-navigation/elements": "^2.9.10",
"@react-navigation/native": "^7.1.33",
"@rn-primitives/portal": "^1.4.0",
"@rn-primitives/slot": "^1.4.0",
"@tanstack/react-query": "^5.99.0",
"axios": "^1.15.0",
"babel-preset-expo": "^55.0.18",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"expo": "~55.0.15",
"expo-application": "~55.0.14",
"expo-constants": "~55.0.14",
@@ -34,16 +40,22 @@
"expo-system-ui": "~55.0.15",
"expo-web-browser": "~55.0.14",
"lucide-react-native": "^1.8.0",
"nativewind": "^4.2.3",
"prettier-plugin-tailwindcss": "^0.5.14",
"react": "19.2.0",
"react-dom": "19.2.0",
"react-native": "0.83.4",
"react-native-gesture-handler": "~2.30.0",
"react-native-reanimated": "4.2.1",
"react-native-reanimated": "^4.2.1",
"react-native-safe-area-context": "~5.6.2",
"react-native-screens": "~4.23.0",
"react-native-tcp-socket": "^6.4.1",
"react-native-web": "~0.21.0",
"react-native-worklets": "0.7.2",
"socket.io-client": "^4.8.3",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^3.4.19",
"tailwindcss-animate": "^1.0.7",
"zod": "^4.3.6",
"zustand": "^5.0.12"
},

View File

@@ -0,0 +1,300 @@
const { withDangerousMod } = require("@expo/config-plugins");
const fs = require("fs");
const path = require("path");
// const packageName = "net.alpla.lst.mobile";
// const packagePath = "com/alpla/lst/mobile";
const packageName = "net.alpla.lst.mobile";
const packagePath = "net/alpla/lst/mobile";
// const packageName = config.android?.package;
// const packagePath = packageName.replace(/\./g, "/");
const moduleCode = `package ${packageName}.scanner
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import com.facebook.react.bridge.*
import com.facebook.react.modules.core.DeviceEventManagerModule
class ZebraScannerModule(
private val reactContext: ReactApplicationContext
) : ReactContextBaseJavaModule(reactContext) {
override fun getName(): String = "ZebraScanner"
private val scanAction = "com.lst.mobile.SCAN"
private var receiverRegistered = false
private val scanReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
println("LST SCANNER: received intent -> \${intent?.action}")
if (intent?.action != scanAction) {
println("LST SCANNER: wrong action")
return
}
val barcodeData: String? =
intent.getStringExtra("com.symbol.datawedge.data_string")
val labelType: String? =
intent.getStringExtra("com.symbol.datawedge.label_type")
val source: String? =
intent.getStringExtra("com.symbol.datawedge.source")
println("LST SCANNER: data=$barcodeData label=$labelType source=$source")
if (barcodeData.isNullOrBlank()) {
println("LST SCANNER: empty barcode")
return
}
val payload = Arguments.createMap().apply {
putString("data", barcodeData)
putString("labelType", labelType)
putString("source", source)
putDouble("timestamp", System.currentTimeMillis().toDouble())
}
sendEvent("barcodeScanned", payload)
}
}
@ReactMethod
fun startListening() {
if (receiverRegistered) return
reactContext.registerReceiver(
scanReceiver,
IntentFilter(scanAction),
Context.RECEIVER_EXPORTED
)
receiverRegistered = true
}
@ReactMethod
fun stopListening() {
if (!receiverRegistered) return
try {
reactContext.unregisterReceiver(scanReceiver)
} catch (_: Exception) {}
receiverRegistered = false
}
/**
* Required for React Native NativeEventEmitter
*/
@ReactMethod
fun addListener(eventName: String) {
// No-op
}
/**
* Required for React Native NativeEventEmitter
*/
@ReactMethod
fun removeListeners(count: Int) {
// No-op
}
@ReactMethod
fun triggerScan() {
val intent = Intent().apply {
action = "com.symbol.datawedge.api.ACTION"
putExtra("com.symbol.datawedge.api.SOFT_SCAN_TRIGGER", "TOGGLE_SCANNING")
}
reactContext.sendBroadcast(intent)
}
private fun sendCommand(command: String, value: Any) {
val intent = Intent().apply {
action = "com.symbol.datawedge.api.ACTION"
when (value) {
is String -> putExtra(command, value)
is Bundle -> putExtra(command, value)
}
}
reactContext.sendBroadcast(intent)
}
private fun sendEvent(eventName: String, payload: WritableMap) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, payload)
}
//
@ReactMethod
fun ensureProfile() {
val profileName = "LST_MOBILE"
// Create profile (safe to call even if it exists)
sendCommand(
"com.symbol.datawedge.api.CREATE_PROFILE",
profileName
)
Thread.sleep(500)
// Configure profile
val profileConfig = Bundle().apply {
putString("PROFILE_NAME", profileName)
putString("PROFILE_ENABLED", "true")
putString("CONFIG_MODE", "CREATE_IF_NOT_EXIST")
val barcodeConfig = Bundle().apply {
putString("PLUGIN_NAME", "BARCODE")
putString("RESET_CONFIG", "true")
val props = Bundle().apply {
putString("scanner_input_enabled", "true")
putString("scanner_selection", "auto")
putString("trigger_mode", "2") // 2 = HARD trigger only (recommended) wakes scanner up
}
putBundle("PARAM_LIST", props)
}
val intentConfig = Bundle().apply {
putString("PLUGIN_NAME", "INTENT")
putString("RESET_CONFIG", "true")
val props = Bundle().apply {
putString("intent_output_enabled", "true")
putString("intent_action", scanAction)
putString("intent_delivery", "2") // broadcast
putString("intent_use_content_provider", "false") // optional but helps
}
putBundle("PARAM_LIST", props)
}
val keystrokeConfig = Bundle().apply {
putString("PLUGIN_NAME", "KEYSTROKE")
putString("RESET_CONFIG", "true")
val props = Bundle().apply {
putString("keystroke_output_enabled", "false")
}
putBundle("PARAM_LIST", props)
}
putParcelableArrayList(
"PLUGIN_CONFIG",
arrayListOf(barcodeConfig, intentConfig, keystrokeConfig)
)
}
sendCommand("com.symbol.datawedge.api.SET_CONFIG", profileConfig)
// Associate with your app
val appConfig = Bundle().apply {
putString("PACKAGE_NAME", reactContext.packageName)
putStringArray("ACTIVITY_LIST", arrayOf("*"))
}
val associateConfig = Bundle().apply {
putString("PROFILE_NAME", profileName)
putString("CONFIG_MODE", "UPDATE")
putParcelableArray("APP_LIST", arrayOf(appConfig))
}
sendCommand("com.symbol.datawedge.api.SET_CONFIG", associateConfig)
}
}
`;
const packageCode = `package ${packageName}.scanner
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class ZebraScannerPackage : ReactPackage {
override fun createNativeModules(
reactContext: ReactApplicationContext
): List<NativeModule> {
return listOf(ZebraScannerModule(reactContext))
}
override fun createViewManagers(
reactContext: ReactApplicationContext
): List<ViewManager<*, *>> {
return emptyList()
}
}
`;
function patchMainApplication(mainApplicationPath) {
let contents = fs.readFileSync(mainApplicationPath, "utf8");
const importLine = `import ${packageName}.scanner.ZebraScannerPackage`;
if (!contents.includes(importLine)) {
contents = contents.replace(
/import com\.facebook\.react\.PackageList/,
`import com.facebook.react.PackageList\n${importLine}`,
);
}
if (!contents.includes("add(ZebraScannerPackage())")) {
contents = contents.replace(
/PackageList\(this\)\.packages\.apply\s*\{/,
`PackageList(this).packages.apply {\n add(ZebraScannerPackage())`,
);
}
fs.writeFileSync(mainApplicationPath, contents);
}
module.exports = function withZebraScanner(config) {
return withDangerousMod(config, [
"android",
async (config) => {
const androidRoot = config.modRequest.platformProjectRoot;
const scannerDir = path.join(
androidRoot,
"app/src/main/java",
packagePath,
"scanner",
);
fs.mkdirSync(scannerDir, { recursive: true });
fs.writeFileSync(
path.join(scannerDir, "ZebraScannerModule.kt"),
moduleCode,
);
fs.writeFileSync(
path.join(scannerDir, "ZebraScannerPackage.kt"),
packageCode,
);
const mainApplicationPath = path.join(
androidRoot,
"app/src/main/java",
packagePath,
"MainApplication.kt",
);
patchMainApplication(mainApplicationPath);
return config;
},
]);
};

View File

@@ -0,0 +1,39 @@
import { Tabs } from "expo-router";
import { Home, Settings } from "lucide-react-native";
import { useAppStore } from "../../hooks/useAppStore";
export default function TabsLayout() {
const serverPort = useAppStore((s) => s.serverPort);
return (
<Tabs
screenOptions={{
headerShown: false, // Hides the header for all screens in this navigator
}}
>
<Tabs.Screen
name="scanner"
options={{
title: "Scan",
tabBarIcon: ({ color, size }) => <Home size={size} color={color} />,
}}
/>
<Tabs.Screen
name="config"
options={{
title: "settings",
tabBarIcon: ({ color, size }) => (
<Settings size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="logs"
options={{
title: "Logs",
href:
parseInt(serverPort || "0", 10) >= 50000 ? null : "/(tabs)/logs",
}}
/>
</Tabs>
);
}

View File

@@ -0,0 +1,7 @@
import { Link } from "expo-router";
import { Text, View } from "react-native";
import Setup from "../setup";
export default function SettingsTab() {
return <Setup />
}

View File

@@ -0,0 +1,13 @@
import React from 'react'
import { Text, View } from 'react-native'
export default function Logs() {
return (
<View style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}><Text>Logs</Text></View>
)
}

View File

@@ -0,0 +1,22 @@
import React from "react";
import { View } from "react-native";
import { useAppStore } from "../../hooks/useAppStore";
import ProdScanner from "../../components/ProdScanner";
import LSTScanner from "../../components/LSTScanner";
export default function scanner() {
const serverPort = useAppStore((s) => s.serverPort);
return (
<View
style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}
>
{parseInt(serverPort || "0", 10) >= 50000 ? <ProdScanner /> : <LSTScanner />}
</View>
);
}

View File

@@ -1,5 +1,7 @@
import { Stack } from "expo-router";
import { StatusBar } from "expo-status-bar";
import "../../global.css";
import { PortalHost } from "@rn-primitives/portal";
export default function RootLayout() {
return (
@@ -9,6 +11,7 @@ export default function RootLayout() {
<Stack.Screen name="index" />
{/* <Stack.Screen name="(tabs)" /> */}
</Stack>
<PortalHost />
</>
);
}

View File

@@ -1,4 +1,4 @@
import { useRouter } from "expo-router";
import { Redirect, useRouter } from "expo-router";
import { useEffect, useState } from "react";
import { ActivityIndicator, Text, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore";
@@ -7,6 +7,7 @@ import { devDelay } from "../lib/devMode";
export default function Index() {
const router = useRouter();
const [message, setMessage] = useState(<Text>Starting app...</Text>);
const [ready, setReady] = useState(false);
const hasHydrated = useAppStore((s) => s.hasHydrated);
const serverPort = useAppStore((s) => s.serverPort);
@@ -40,13 +41,18 @@ export default function Index() {
</Text>,
);
await devDelay(1500);
router.replace("/scanner");
//router.replace("/scanner");
setReady(true);
return;
}
setMessage(<Text>Checking for updates</Text>);
await devDelay(1500);
// TODO if theres an update go to update screen message :D
setMessage(<Text>Opening LST scan app</Text>);
await devDelay(3250);
router.replace("/scanner");
//router.replace("/scanner");
setReady(true);
} catch (error) {
console.log("Startup error", error);
setMessage(<Text>Something went wrong during startup.</Text>);
@@ -56,6 +62,9 @@ export default function Index() {
startup();
}, [hasHydrated, hasValidSetup, serverPort, router]);
if (ready) {
return <Redirect href="/(tabs)/scanner" />;
}
return (
<View
style={{

View File

@@ -1,32 +0,0 @@
import React from "react";
import { Text, View } from "react-native";
export default function scanner() {
return (
<View
style={{
flex: 1,
//justifyContent: "center",
alignItems: "center",
marginTop: 50,
}}
>
<View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
</View>
<View
style={{
marginTop: 50,
alignItems: "center",
}}
>
<Text>Relocate</Text>
<Text>0 / 4</Text>
</View>
{/* <View>
<Text>List of recent scanned pallets TBA</Text>
</View> */}
</View>
);
}

View File

@@ -4,7 +4,7 @@ import { useState } from "react";
import { Alert, Button, Text, TextInput, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore";
export default function setup() {
export default function Setup() {
const router = useRouter();
const [auth, setAuth] = useState(false);
const [pin, setPin] = useState("");
@@ -40,6 +40,7 @@ export default function setup() {
updateAppState({
serverIp: serverIp.trim(),
serverPort: serverPort.trim(),
scannerId: scannerId?.trim(),
setupCompleted: true,
isRegistered: true,
});
@@ -80,7 +81,7 @@ export default function setup() {
borderRadius: 8,
}}
>
<Button title="Save Config" onPress={authCheck} />
<Button title="Submit" onPress={authCheck} />
</View>
</View>
) : (
@@ -136,7 +137,7 @@ export default function setup() {
<Button
title="Home"
onPress={() => {
router.push("/");
router.push("/(tabs)/scanner");
}}
/>
</View>

View File

@@ -0,0 +1,24 @@
import React from 'react'
import { View, Text } from 'react-native'
export default function LSTScanner() {
return (
<View><View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>LST Scanner</Text>
</View>
<View
style={{
marginTop: 50,
alignItems: "center",
}}
>
<Text>Relocate</Text>
<Text>0 / 4</Text>
</View>
{/* <View>
<Text>List of recent scanned pallets TBA</Text>
</View> */}
</View>
)
}

View File

@@ -0,0 +1,121 @@
import { useCallback, useEffect, useState } from "react";
import { Text, View } from "react-native";
import { useAppStore } from "../hooks/useAppStore";
import { sendTcpMessage } from "../lib/tcpScan";
import { type ZebraScanResult, zebraScanner } from "../lib/ZebraScanner";
import { ScannedLabelBox } from "./ScannedLabels";
const STX = "\x02";
const ETX = "\x03";
export default function ProdScanner() {
const [lastScan, setLastScan] = useState<any>(null);
const [tagScans, setTagScans] = useState<any>([]);
const scannerIdFromStore = useAppStore((s) => s.scannerId);
const serverIp = useAppStore((s) => s.serverIp);
const serverPort = useAppStore((s) => s.serverPort);
const handleScan = useCallback(
async (scan: ZebraScanResult) => {
const scanned = scan.data;
let commandToSend = `${STX}${scannerIdFromStore}@${scanned}${ETX}`;
// if we are sscc we need to scan like this .... <STX>98@]C100090087710038712256<ETX>
if (scan.data.startsWith("000")) {
commandToSend = `${STX}${scannerIdFromStore}@]C1${scanned}${ETX}`;
setTagScans((prev: any) => [
parseInt(scanned.slice(10, -1) || "000", 10).toString(),
...prev,
]);
}
// if we change commands we want to zero out the last scanned labels
if (/^[a-zA-Z]/.test(scan.data)) {
setTagScans([]);
}
const something = await sendTcpMessage(
commandToSend,
serverIp,
parseInt(serverPort || "0", 10),
);
// Later this is where your TCP send goes.
// const response = await sendTcpMessage(tcpMessage);
setLastScan(something.data[0]);
//console.log("TCP response:", something);
},
[scannerIdFromStore, serverIp, serverPort],
);
console.log(lastScan);
useEffect(() => {
zebraScanner.ensureProfile();
zebraScanner.startListening();
const sub = zebraScanner.addScanListener((scan) => {
//console.log("SCAN:", scan);
handleScan(scan);
});
return () => {
sub.remove();
zebraScanner.stopListening();
};
}, [handleScan]);
return (
<View>
<View>
<View style={{ alignItems: "center", margin: 10 }}>
<Text style={{ fontSize: 20, fontWeight: "600" }}>
Scanner ID: {parseInt(scannerIdFromStore || "0", 10)}
</Text>
</View>
{!lastScan ? (
<View
style={{
marginTop: 10,
alignItems: "center",
}}
>
<Text className="text-xl font-bold">Waiting on scan....</Text>
</View>
) : (
<View
style={{
marginTop: 10,
alignItems: "center",
}}
>
<Text style={{ fontSize: 20, fontWeight: "600" }}>
{lastScan?.action}
</Text>
{lastScan?.type === "error" ? (
<Text style={{ fontSize: 20, fontWeight: "600" }}>
{lastScan?.message}
</Text>
) : (
<View
style={{
marginTop: 15,
alignItems: "center",
}}
>
<Text style={{ fontSize: 20, fontWeight: "600" }}>
{lastScan?.prompt}
</Text>
<Text style={{ fontSize: 20, fontWeight: "600" }}>
{lastScan?.message}
</Text>
</View>
)}
</View>
)}
</View>
<ScannedLabelBox labels={tagScans} />
</View>
);
}

View File

@@ -0,0 +1,58 @@
import { useEffect, useState } from "react";
import { Button, Text, View } from "react-native";
import { sendTcpMessage } from "../lib/tcpScan";
import { type ZebraScanResult, zebraScanner } from "../lib/ZebraScanner";
const STX = "\x02";
const ETX = "\x03";
export function ScannerTestScreen() {
const [lastResponse, setLastResponse] = useState("");
const handleScan = async (scan: ZebraScanResult) => {
console.log("Raw Zebra scan:", scan);
const scanned = scan.data;
let commandToSend = `${STX}98@${scanned}${ETX}`;
// if we are sscc we need to scan like this .... <STX>98@]C100090087710038712256<ETX>
if (scan.data.startsWith("000")) {
commandToSend = `${STX}98@]C1${scanned}${ETX}`;
}
const something = await sendTcpMessage(commandToSend, "10.44.0.26", 50001);
// Later this is where your TCP send goes.
// const response = await sendTcpMessage(tcpMessage);
console.log("TCP response:", something);
setLastResponse(JSON.stringify(something));
};
useEffect(() => {
zebraScanner.ensureProfile();
zebraScanner.startListening();
const sub = zebraScanner.addScanListener((scan) => {
console.log("SCAN:", scan);
handleScan(scan);
});
return () => {
sub.remove();
zebraScanner.stopListening();
};
}, []);
return (
<View style={{ padding: 20, gap: 12 }}>
<Button
title="Soft Trigger Scan"
onPress={() => zebraScanner.triggerScan()}
/>
<Text>Waiting for scan...</Text>
<Text>{lastResponse}</Text>
</View>
);
}

View File

@@ -0,0 +1,50 @@
import { ScrollView, Text, View } from "react-native";
type ScannedLabel = {
id: string;
barcode: string;
createdAt: string;
};
type ScannedLabelBoxProps = {
labels: ScannedLabel[];
};
export function ScannedLabelBox({ labels }: ScannedLabelBoxProps) {
return (
<View style={{ flex: 1, marginTop: 30 }}>
<Text style={{ fontSize: 18, fontWeight: "700", marginBottom: 8 }}>
Current scanned labels
</Text>
<ScrollView
style={{
flex: 1,
borderWidth: 1,
borderColor: "#ccc",
borderRadius: 8,
padding: 2,
margin: 2,
}}
contentContainerStyle={{ gap: 2 }}
>
{labels.length === 0 ? (
<Text style={{ color: "#777" }}>No labels scanned yet</Text>
) : (
labels.map((label) => (
<View
key={`${label}`}
style={{
padding: 2,
borderRadius: 8,
backgroundColor: "#f2f2f2",
}}
>
<Text style={{ fontSize: 18, fontWeight: "700" }}>{label}</Text>
</View>
))
)}
</ScrollView>
</View>
);
}

View File

@@ -0,0 +1,52 @@
import { Text, TextClassContext } from '@/components/ui/text';
import { cn } from '@/lib/utils';
import { View } from 'react-native';
function Card({ className, ...props }: React.ComponentProps<typeof View>) {
return (
<TextClassContext.Provider value="text-card-foreground">
<View
className={cn(
'bg-card border-border flex flex-col gap-6 rounded-xl border py-6 shadow-sm shadow-black/5',
className
)}
{...props}
/>
</TextClassContext.Provider>
);
}
function CardHeader({ className, ...props }: React.ComponentProps<typeof View>) {
return <View className={cn('flex flex-col gap-1.5 px-6', className)} {...props} />;
}
function CardTitle({
className,
...props
}: React.ComponentProps<typeof Text>) {
return (
<Text
role="heading"
aria-level={3}
className={cn('font-semibold leading-none', className)}
{...props}
/>
);
}
function CardDescription({
className,
...props
}: React.ComponentProps<typeof Text>) {
return <Text className={cn('text-muted-foreground text-sm', className)} {...props} />;
}
function CardContent({ className, ...props }: React.ComponentProps<typeof View>) {
return <View className={cn('px-6', className)} {...props} />;
}
function CardFooter({ className, ...props }: React.ComponentProps<typeof View>) {
return <View className={cn('flex flex-row items-center px-6', className)} {...props} />;
}
export { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle };

View File

@@ -0,0 +1,88 @@
import { cn } from '@/lib/utils';
import * as Slot from '@rn-primitives/slot';
import { cva, type VariantProps } from 'class-variance-authority';
import * as React from 'react';
import { Platform, Text as RNText, type Role } from 'react-native';
const textVariants = cva(
cn(
'text-foreground text-base',
Platform.select({
web: 'select-text',
})
),
{
variants: {
variant: {
default: '',
h1: cn(
'text-center text-4xl font-extrabold tracking-tight',
Platform.select({ web: 'scroll-m-20 text-balance' })
),
h2: cn(
'border-border border-b pb-2 text-3xl font-semibold tracking-tight',
Platform.select({ web: 'scroll-m-20 first:mt-0' })
),
h3: cn('text-2xl font-semibold tracking-tight', Platform.select({ web: 'scroll-m-20' })),
h4: cn('text-xl font-semibold tracking-tight', Platform.select({ web: 'scroll-m-20' })),
p: 'mt-3 leading-7 sm:mt-6',
blockquote: 'mt-4 border-l-2 pl-3 italic sm:mt-6 sm:pl-6',
code: cn(
'bg-muted relative rounded px-[0.3rem] py-[0.2rem] font-mono text-sm font-semibold'
),
lead: 'text-muted-foreground text-xl',
large: 'text-lg font-semibold',
small: 'text-sm font-medium leading-none',
muted: 'text-muted-foreground text-sm',
},
},
defaultVariants: {
variant: 'default',
},
}
);
type TextVariantProps = VariantProps<typeof textVariants>;
type TextVariant = NonNullable<TextVariantProps['variant']>;
const ROLE: Partial<Record<TextVariant, Role>> = {
h1: 'heading',
h2: 'heading',
h3: 'heading',
h4: 'heading',
blockquote: Platform.select({ web: 'blockquote' as Role }),
code: Platform.select({ web: 'code' as Role }),
};
const ARIA_LEVEL: Partial<Record<TextVariant, string>> = {
h1: '1',
h2: '2',
h3: '3',
h4: '4',
};
const TextClassContext = React.createContext<string | undefined>(undefined);
function Text({
className,
asChild = false,
variant = 'default',
...props
}: React.ComponentProps<typeof RNText> &
TextVariantProps & {
asChild?: boolean;
}) {
const textClass = React.useContext(TextClassContext);
const Component = asChild ? Slot.Text : RNText;
return (
<Component
className={cn(textVariants({ variant }), textClass, className)}
role={variant ? ROLE[variant] : undefined}
aria-level={variant ? ARIA_LEVEL[variant] : undefined}
{...props}
/>
);
}
export { Text, TextClassContext };

View File

@@ -0,0 +1,40 @@
import {
type EmitterSubscription,
NativeEventEmitter,
NativeModules,
} from "react-native";
const { ZebraScanner } = NativeModules;
const scannerEmitter = new NativeEventEmitter(ZebraScanner);
export type ZebraScanResult = {
data: string;
labelType?: string;
source?: string;
timestamp: number;
};
export const zebraScanner = {
startListening() {
ZebraScanner.startListening();
},
stopListening() {
ZebraScanner.stopListening();
},
triggerScan() {
ZebraScanner.triggerScan();
},
ensureProfile() {
ZebraScanner.ensureProfile();
},
addScanListener(
callback: (scan: ZebraScanResult) => void,
): EmitterSubscription {
return scannerEmitter.addListener("barcodeScanned", callback);
},
};

View File

@@ -0,0 +1,207 @@
import TcpSocket from "react-native-tcp-socket";
// const STX = "\x02";
// const ETX = "\x03";
type TcpResponse = {
success: boolean;
message: string;
data: string[];
};
function parseErpResponse(buffer: Buffer) {
const text = buffer
.toString("utf8")
.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]|#[0-9A-Za-z])/g, "")
.replace(/\x02/g, "")
.replace(/\x03/g, "")
.trim();
const noHeader = text.replace(/^\d+@/, "");
console.log(text);
if (!noHeader.includes("Scan:")) {
return {
raw: text,
type: "error",
message: noHeader.trim(),
lines: [noHeader.trim()],
};
}
const [actionPart, scanPart = ""] = noHeader.split("Scan:");
const action = actionPart.trim();
const scanClean = scanPart.trim();
const successMatch = scanClean.match(/^(.*?)\s+V$/);
if (successMatch) {
const prompt = successMatch[1].trim();
return {
raw: text,
type: "success",
action,
prompt,
status: "V",
lines: [action, prompt, "V"],
};
}
// // Handles: "Production lotInvalid barcode"
// const knownErrors = [
// "Invalid barcode",
// "Invalid machine",
// "Not on stock",
// "Article tolerance for consolidation not satisfied",
// ].sort((a, b) => b.length - a.length);
// const foundError = knownErrors.find((err) => scanClean.includes(err));
// if (foundError) {
// const prompt = scanClean.replace(foundError, "").trim();
// return {
// raw: text,
// type: "error",
// action,
// prompt,
// message: foundError,
// lines: [action, prompt, foundError].filter(Boolean),
// };
// }
// return {
// raw: text,
// type: "pending",
// action,
// prompt: scanClean,
// lines: [action, scanClean].filter(Boolean),
// };
const unitMatch = scanClean.match(/^(Unit\s+\d+\/\d+)(.*)$/);
if (unitMatch) {
const prompt = unitMatch[1].trim(); // "Unit 1/4"
const remainder = unitMatch[2].trim(); // everything after
// SUCCESS
if (remainder === "V") {
return {
raw: text,
type: "success",
action,
prompt,
status: "V",
lines: [action, prompt, "V"],
};
}
// Known ERP errors
const knownErrors = [
"Invalid barcode",
"Invalid machine",
"Not on stock",
"Article tolerance for consolidation not satisfied",
];
const foundError = knownErrors.find((err) =>
remainder.toLowerCase().includes(err.toLowerCase()),
);
if (foundError) {
return {
raw: text,
type: "error",
action,
prompt,
message: foundError,
lines: [action, prompt, foundError],
};
}
if (remainder) {
return {
raw: text,
type: "prompt",
action,
prompt,
message: remainder,
lines: [action, prompt, remainder],
};
}
return {
raw: text,
type: "pending",
action,
prompt,
lines: [action, prompt],
};
}
}
/**
* Sends a Zebra-style TCP message:
* <STX>98@{scanned}<ETX>
*/
export async function sendTcpMessage(
command: string,
host: string,
port: number,
timeoutMs = 5000,
): Promise<TcpResponse> {
return new Promise((resolve) => {
const responses: any = [];
const client = TcpSocket.createConnection({ host, port }, () => {
console.log("Sending TCP (visible):", `${command}`);
client.write(command);
});
const timeout = setTimeout(() => {
client.destroy();
resolve({
success: false,
message: "TCP timeout",
data: responses,
});
}, timeoutMs);
client.on("data", (data) => {
//const text = data.toString();
//console.log("TCP received:", text);
const parsed = parseErpResponse(data);
responses.push(parsed);
clearTimeout(timeout);
resolve({
success: true,
message: "TCP Response",
data: responses,
});
});
client.on("error", (err) => {
clearTimeout(timeout);
client.destroy();
resolve({
success: false,
message: err.message,
data: responses,
});
});
client.on("close", () => {
clearTimeout(timeout);
resolve({
success: true,
message: "TCP complete",
data: responses,
});
});
});
}

View File

@@ -0,0 +1,81 @@
import { DarkTheme, DefaultTheme, type Theme } from "@react-navigation/native";
export const THEME = {
light: {
background: "hsl(0 0% 100%)",
foreground: "hsl(0 0% 3.9%)",
card: "hsl(0 0% 100%)",
cardForeground: "hsl(0 0% 3.9%)",
popover: "hsl(0 0% 100%)",
popoverForeground: "hsl(0 0% 3.9%)",
primary: "hsl(0 0% 9%)",
primaryForeground: "hsl(0 0% 98%)",
secondary: "hsl(0 0% 96.1%)",
secondaryForeground: "hsl(0 0% 9%)",
muted: "hsl(0 0% 96.1%)",
mutedForeground: "hsl(0 0% 45.1%)",
accent: "hsl(0 0% 96.1%)",
accentForeground: "hsl(0 0% 9%)",
destructive: "hsl(0 84.2% 60.2%)",
border: "hsl(0 0% 89.8%)",
input: "hsl(0 0% 89.8%)",
ring: "hsl(0 0% 63%)",
radius: "0.625rem",
chart1: "hsl(12 76% 61%)",
chart2: "hsl(173 58% 39%)",
chart3: "hsl(197 37% 24%)",
chart4: "hsl(43 74% 66%)",
chart5: "hsl(27 87% 67%)",
},
dark: {
background: "hsl(0 0% 3.9%)",
foreground: "hsl(0 0% 98%)",
card: "hsl(0 0% 3.9%)",
cardForeground: "hsl(0 0% 98%)",
popover: "hsl(0 0% 3.9%)",
popoverForeground: "hsl(0 0% 98%)",
primary: "hsl(0 0% 98%)",
primaryForeground: "hsl(0 0% 9%)",
secondary: "hsl(0 0% 14.9%)",
secondaryForeground: "hsl(0 0% 98%)",
muted: "hsl(0 0% 14.9%)",
mutedForeground: "hsl(0 0% 63.9%)",
accent: "hsl(0 0% 14.9%)",
accentForeground: "hsl(0 0% 98%)",
destructive: "hsl(0 70.9% 59.4%)",
border: "hsl(0 0% 14.9%)",
input: "hsl(0 0% 14.9%)",
ring: "hsl(300 0% 45%)",
radius: "0.625rem",
chart1: "hsl(220 70% 50%)",
chart2: "hsl(160 60% 45%)",
chart3: "hsl(30 80% 55%)",
chart4: "hsl(280 65% 60%)",
chart5: "hsl(340 75% 55%)",
},
};
export const NAV_THEME: Record<"light" | "dark", Theme> = {
light: {
...DefaultTheme,
colors: {
background: THEME.light.background,
border: THEME.light.border,
card: THEME.light.card,
notification: THEME.light.destructive,
primary: THEME.light.primary,
text: THEME.light.foreground,
},
},
dark: {
...DarkTheme,
colors: {
background: THEME.dark.background,
border: THEME.dark.border,
card: THEME.dark.card,
notification: THEME.dark.destructive,
primary: THEME.dark.primary,
text: THEME.dark.foreground,
},
},
};

View File

@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

View File

@@ -0,0 +1,76 @@
const { hairlineWidth } = require("nativewind/theme");
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
presets: [require("nativewind/preset")],
theme: {
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
},
borderRadius: {
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
borderWidth: {
hairline: hairlineWidth(),
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
future: {
hoverOnlyWhenSupported: true,
},
plugins: [require("tailwindcss-animate")],
};

View File

@@ -4,7 +4,8 @@
"strict": true,
"paths": {
"@/*": [
"./src/*"
"./src/*",
"./*"
],
"@/assets/*": [
"./assets/*"
@@ -15,6 +16,7 @@
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts"
"expo-env.d.ts",
"nativewind-env.d.ts"
]
}

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "lst_v3",
"version": "0.0.1-alpha.5",
"version": "0.0.2-alpha.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lst_v3",
"version": "0.0.1-alpha.5",
"version": "0.0.2-alpha.6",
"license": "ISC",
"dependencies": {
"@dotenvx/dotenvx": "^1.57.0",

View File

@@ -1,6 +1,6 @@
{
"name": "lst_v3",
"version": "0.0.1-alpha.5",
"version": "0.0.2-alpha.6",
"description": "The tool that supports us in our everyday alplaprod",
"main": "index.js",
"scripts": {
@@ -24,7 +24,7 @@
"version": "changeset version",
"specCheck": "node scripts/check-route-specs.mjs",
"commit": "cz",
"release": "commit-and-tag-version",
"release": "npm run build && commit-and-tag-version",
"build:apk": "cd lstMobile && expo prebuild --clean && cd android && gradlew.bat assembleRelease "
},
"repository": {

View File

@@ -0,0 +1,12 @@
$source = "C:\Sources\AlplaPROD\MasterLayouts"
$servers = @("USMCD1VMS036", "USSTP1VMS006", "USLIM1VMS006")
$log = "C:\Sources\AlplaPROD\MasterLayouts\sync.log"
foreach ($server in $servers) {
Add-Content $log "======================================"
Add-Content $log "Syncing to $server at $(Get-Date)"
robocopy $source "\\$server\C$\Sources\AlplaPROD\MasterLayouts" /E /Z /R:2 /W:5 /FFT /XO /TEE /LOG+:$log
Add-Content $log "Finished $server with exit code $LASTEXITCODE"
}

View File

@@ -1,132 +0,0 @@
docker build -t git.tuffraid.net/cowch/lst_v3:latest .
docker push git.tuffraid.net/cowch/lst_v3:latest
docker compose pull && docker compose up -d --force-recreate
How to choose the bump
Use this rule:
patch = bug fix, small safe improvement
minor = new feature, backward compatible
major = breaking change
Changesets uses semver bump ty
### daily process
npm commit
- when closing a issue at the end add
Use one of these in the commit body or PR description:
- - Closes #123
- - Fixes #123
- - Resolves #123
Common ones:
- - Closes #123
- - Fixes #123
- - Resolves #123
Reference an issue without closing it
Use:
- - Refs #123
- - Related to #123
- - See #123
Good safe one:
- - Refs #123
Good example commit
Subject:
- - fix(cors): normalize external url origin
Body:
- - Refs #42
Or if this should close it:
- - Closes #42
# Release flow
npm run changeset:add
Pick one:
- patch = bug fix
- minor = new feature, non-breaking
- major = breaking change
Edit the generated .md file in .changeset it will be randomly named and add anything else in here from all the commits that are new to this release
Recommended release command
npm run changeset:version
stage the change log file
git commit -m "chore(release): version packages"
git tag v0.0.1-alpha.0 change this to the same version thats in the pkg.json
then push it
git push
git push --tags
### release type
when we want to go from alpha to normal well do
npx changeset pre enter alpha
npx changeset pre enter rc
go to full production
npx changeset pre exit
npx changeset version
### Steps will make it cleaner later
Daily work
1. Stage files
2. npm run commit
3. Add issue keyword if needed
4. git push when ready
Release flow
1. npx changeset
2. pick patch/minor/major
3. edit the generated md file with better notes
4. npx changeset version
5. git add .
6. git commit -m "chore(release): version packages"
7. git tag vX.X.X
8. git push
9. git push --tags
# normal work
stage files
npm run commit
# if releasing
npm run commit
npm run release -- --prerelease alpha
git push
git push --tags
git add .
git commit -m "chore(release): version packages"
git tag v0.0.1-alpha.0
git push
git push --tags