@stacksjs/actions 0.70.371 → 0.70.376

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.
@@ -0,0 +1,20 @@
1
+ import type { AndroidMobileConfig } from '@stacksjs/types';
2
+ export declare function toCraftAndroidConfig(config: AndroidMobileConfig): CraftAndroidConfig;
3
+ export declare function validateAndroidMobileConfig(config: AndroidMobileConfig): void;
4
+ export declare interface CraftAndroidConfig {
5
+ [key: string]: unknown
6
+ appName: string
7
+ packageName: string
8
+ version?: string
9
+ versionCode?: number
10
+ minSdk?: number
11
+ targetSdk?: number
12
+ darkMode?: boolean
13
+ backgroundColor?: string
14
+ devServerURL?: string
15
+ trustedOrigins?: string[]
16
+ urlSchemes?: string[]
17
+ appIconPath?: string
18
+ googleServicesFile?: string
19
+ enableHealthConnect?: boolean
20
+ }
@@ -0,0 +1 @@
1
+ import{normalizeMobileUrl}from"./ios-config";const CAPABILITY_KEYS={speechRecognition:"enableSpeechRecognition",haptics:"enableHaptics",share:"enableShare",camera:"enableCamera",biometric:"enableBiometric",pushNotifications:"enablePushNotifications",secureStorage:"enableSecureStorage",geolocation:"enableGeolocation",backgroundLocation:"enableBackgroundLocation",keepAwake:"enableKeepAwake",deepLinks:"enableDeepLinks",healthConnect:"enableHealthConnect"};export function toCraftAndroidConfig(config){const devServerURL=normalizeMobileUrl(config.url),trustedOrigins=new Set(config.trustedOrigins??[]);if(devServerURL)trustedOrigins.add(new URL(devServerURL).origin);const craft={appName:config.appName,packageName:config.packageName,version:config.version,versionCode:config.versionCode,minSdk:config.minSdk,targetSdk:config.targetSdk,darkMode:config.darkMode,backgroundColor:config.backgroundColor,devServerURL,trustedOrigins:[...trustedOrigins],urlSchemes:config.urlSchemes,appIconPath:config.appIcon,googleServicesFile:config.googleServicesFile};for(const[key,nativeKey]of Object.entries(CAPABILITY_KEYS)){const enabled=config.capabilities?.[key];if(enabled!==void 0)craft[nativeKey]=enabled}if(config.capabilities?.backgroundLocation)craft.enableGeolocation=!0;return craft}export function validateAndroidMobileConfig(config){if(!config.appName?.trim())throw Error("config/mobile.ts must define android.appName");if(!/^[A-Za-z][A-Za-z0-9_]*(?:\.[A-Za-z][A-Za-z0-9_]*)+$/.test(config.packageName))throw Error(`Invalid Android package name: ${config.packageName}`);if(config.url&&config.webAssets)throw Error("Choose either android.url or android.webAssets in config/mobile.ts, not both");if(!config.url&&!config.webAssets)throw Error("config/mobile.ts must define android.url or android.webAssets");if(config.fallbackWebAssets&&!config.url)throw Error("android.fallbackWebAssets requires android.url");if(config.url){const normalized=normalizeMobileUrl(config.url),url=new URL(normalized),isLocal=["localhost","127.0.0.1","10.0.2.2"].includes(url.hostname);if(url.protocol!=="https:"&&!isLocal)throw Error("android.url must use HTTPS outside local development")}if(config.capabilities?.healthConnect&&(config.minSdk??26)<26)throw Error("Android Health Connect requires android.minSdk 26 or newer");for(const scheme of config.urlSchemes??[])if(!/^[a-z][a-z0-9+.-]*$/i.test(scheme))throw Error(`Invalid Android URL scheme: ${scheme}`)}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import{existsSync,readFileSync,writeFileSync}from"node:fs";import{pathToFileURL}from"node:url";import process from"node:process";import{log}from"@stacksjs/cli";import{projectPath,storagePath}from"@stacksjs/path";import{resolveCraftBuilderProvenance}from"./craft-provenance";import{toCraftAndroidConfig,validateAndroidMobileConfig}from"./android-config";import{resolveMobilePath}from"./ios-config";process.exitCode=1;async function loadCraftAndroidBuilder(){const explicit=process.env.CRAFT_ANDROID_SRC;try{if(explicit)return await import(pathToFileURL(explicit).href);return await import("craft-native/android")}catch(error){throw Error("Craft Android builder is unavailable. Install the current craft-native package or set CRAFT_ANDROID_SRC to packages/android/src/index.ts in a Craft checkout.",{cause:error})}}const configPath=projectPath("config/mobile.ts");if(!existsSync(configPath))throw Error("Missing config/mobile.ts. Add an android section before running `buddy build:android`.");const configModule=await import(`${pathToFileURL(configPath).href}?t=${Date.now()}`),config=configModule.default.android;if(!config)throw Error("config/mobile.ts must define an android section");validateAndroidMobileConfig(config);const output=resolveMobilePath(projectPath(),config.output)??storagePath("framework/mobile/android"),webAssets=resolveMobilePath(projectPath(),config.webAssets),fallbackWebAssets=resolveMobilePath(projectPath(),config.fallbackWebAssets),craftConfig=toCraftAndroidConfig(config);craftConfig.appIconPath=resolveMobilePath(projectPath(),config.appIcon);craftConfig.googleServicesFile=resolveMobilePath(projectPath(),config.googleServicesFile);if(process.env.NODE_ENV==="production"&&config.capabilities?.pushNotifications&&!craftConfig.googleServicesFile)throw Error("Production Android push notifications require android.googleServicesFile in config/mobile.ts");const builder=await loadCraftAndroidBuilder();await builder.init({name:config.appName,packageName:config.packageName,output,config:craftConfig});await builder.build({output,htmlPath:webAssets??fallbackWebAssets,devServer:craftConfig.devServerURL,release:process.env.NODE_ENV==="production",compile:process.env.STACKS_ANDROID_SKIP_GRADLE!=="1"});const sourceRevision=Bun.spawnSync(["git","rev-parse","HEAD"],{cwd:projectPath()}).stdout.toString().trim(),generatedConfig=JSON.parse(readFileSync(`${output}/craft.config.json`,"utf8"));writeFileSync(`${output}/stacks-mobile.json`,`${JSON.stringify({schemaVersion:"1.0.0",platform:"android",sourceRevision,source:webAssets?{kind:"bundled",path:webAssets}:{kind:"remote",url:craftConfig.devServerURL,fallback:fallbackWebAssets?{kind:"bundled",path:fallbackWebAssets}:void 0},capabilities:config.capabilities??{},builder:resolveCraftBuilderProvenance(process.env.CRAFT_ANDROID_SRC),craft:generatedConfig},null,2)}
2
+ `);log.success(`Built the Craft Android project in ${output}`);process.exitCode=0;
@@ -0,0 +1,6 @@
1
+ export declare function resolveCraftBuilderProvenance(explicitSource?: string): CraftBuilderProvenance;
2
+ export declare interface CraftBuilderProvenance {
3
+ package: 'craft-native'
4
+ source: 'package' | 'path'
5
+ revision?: string
6
+ }
@@ -0,0 +1 @@
1
+ import{dirname}from"node:path";export function resolveCraftBuilderProvenance(explicitSource){if(!explicitSource)return{package:"craft-native",source:"package"};const git=Bun.spawnSync(["git","rev-parse","HEAD"],{cwd:dirname(explicitSource)}),revision=git.exitCode===0?git.stdout.toString().trim():"";return{package:"craft-native",source:"path",.../^[\da-f]{40}$/.test(revision)?{revision}:{}}}
@@ -0,0 +1,26 @@
1
+ import type { IosMobileConfig } from '@stacksjs/types';
2
+ export declare function normalizeMobileUrl(value: string | undefined): string | undefined;
3
+ export declare function resolveMobilePath(root: string, value: string | undefined): string | undefined;
4
+ export declare function toCraftIosConfig(config: IosMobileConfig): CraftIosConfig;
5
+ export declare function validateIosMobileConfig(config: IosMobileConfig): void;
6
+ export declare interface CraftIosConfig {
7
+ [key: string]: unknown
8
+ appName: string
9
+ bundleId: string
10
+ version?: string
11
+ buildNumber?: string
12
+ darkMode?: boolean
13
+ backgroundColor?: string
14
+ iosVersion?: string
15
+ watchosVersion?: string
16
+ teamId?: string
17
+ devServerURL?: string
18
+ urlSchemes?: string[]
19
+ trustedOrigins?: string[]
20
+ associatedDomains?: string[]
21
+ appGroups?: string[]
22
+ appIconPath?: string
23
+ privacy?: IosMobileConfig['privacy']
24
+ orientations?: IosMobileConfig['orientations']
25
+ deviceFamilies?: IosMobileConfig['deviceFamilies']
26
+ }
@@ -0,0 +1 @@
1
+ import{isAbsolute,resolve}from"node:path";const CAPABILITY_KEYS={speechRecognition:"enableSpeechRecognition",haptics:"enableHaptics",share:"enableShare",camera:"enableCamera",biometric:"enableBiometric",pushNotifications:"enablePushNotifications",secureStorage:"enableSecureStorage",geolocation:"enableGeolocation",backgroundLocation:"enableBackgroundLocation",clipboard:"enableClipboard",contacts:"enableContacts",calendar:"enableCalendar",localNotifications:"enableLocalNotifications",inAppPurchase:"enableInAppPurchase",keepAwake:"enableKeepAwake",orientationLock:"enableOrientationLock",deepLinks:"enableDeepLinks",qrScanner:"enableQRScanner",filePicker:"enableFilePicker",fileDownload:"enableFileDownload",socialAuth:"enableSocialAuth",audioRecording:"enableAudioRecording",videoRecording:"enableVideoRecording",motionSensors:"enableMotionSensors",localDatabase:"enableLocalDatabase",bluetooth:"enableBluetooth",nfc:"enableNFC",healthKit:"enableHealthKit",liveActivities:"enableLiveActivities",watchApp:"enableWatchApp",backgroundTasks:"enableBackgroundTasks",screenCapture:"enableScreenCapture",pdfViewer:"enablePDFViewer",augmentedReality:"enableAR",machineLearning:"enableMLKit"};export function normalizeMobileUrl(value){const input=value?.trim();if(!input)return;return new URL(/^https?:\/\//i.test(input)?input:`https://${input}`).toString().replace(/\/$/,"")}export function resolveMobilePath(root,value){if(!value)return;return isAbsolute(value)?value:resolve(root,value)}export function toCraftIosConfig(config){const devServerURL=normalizeMobileUrl(config.url),trustedOrigins=new Set(config.trustedOrigins??[]);if(devServerURL)trustedOrigins.add(new URL(devServerURL).origin);const craft={appName:config.appName,bundleId:config.bundleId,version:config.version,buildNumber:config.buildNumber,darkMode:config.darkMode,backgroundColor:config.backgroundColor,iosVersion:config.deploymentTarget,watchosVersion:config.watchDeploymentTarget,teamId:config.teamId,devServerURL,urlSchemes:config.urlSchemes,trustedOrigins:[...trustedOrigins],associatedDomains:config.associatedDomains,appGroups:config.appGroups,appIconPath:config.appIcon,privacy:config.privacy,orientations:config.orientations,deviceFamilies:config.deviceFamilies};for(const[key,nativeKey]of Object.entries(CAPABILITY_KEYS)){const enabled=config.capabilities?.[key];if(enabled!==void 0)craft[nativeKey]=enabled}if(config.capabilities?.backgroundLocation)craft.enableGeolocation=!0;return craft}export function validateIosMobileConfig(config){if(!config.appName?.trim())throw Error("config/mobile.ts must define ios.appName");if(!/^[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+$/.test(config.bundleId))throw Error(`Invalid iOS bundle identifier: ${config.bundleId}`);if(config.url&&config.webAssets)throw Error("Choose either ios.url or ios.webAssets in config/mobile.ts, not both");if(!config.url&&!config.webAssets)throw Error("config/mobile.ts must define ios.url or ios.webAssets");if(config.fallbackWebAssets&&!config.url)throw Error("ios.fallbackWebAssets requires ios.url");if(config.deviceFamilies&&(config.deviceFamilies.length===0||config.deviceFamilies.some((family)=>family!=="iphone"&&family!=="ipad")))throw Error("ios.deviceFamilies must contain iphone and/or ipad");if(config.url){const url=new URL(normalizeMobileUrl(config.url)),isLocal=["localhost","127.0.0.1","::1"].includes(url.hostname);if(url.protocol!=="https:"&&!isLocal)throw Error("ios.url must use HTTPS outside local development")}for(const domain of config.associatedDomains??[])if(!/^(applinks|webcredentials|activitycontinuation):[^/\s]+$/.test(domain))throw Error(`Invalid iOS associated domain: ${domain}`);if(config.capabilities?.watchApp){const target=Number.parseFloat(config.watchDeploymentTarget??"9.0");if(!Number.isFinite(target)||target<9)throw Error("ios.watchDeploymentTarget must be watchOS 9.0 or newer")}}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import{existsSync,readFileSync,writeFileSync}from"node:fs";import{pathToFileURL}from"node:url";import process from"node:process";import{log}from"@stacksjs/cli";import{projectPath,storagePath}from"@stacksjs/path";import{resolveCraftBuilderProvenance}from"./craft-provenance";import{resolveMobilePath,toCraftIosConfig,validateIosMobileConfig}from"./ios-config";process.exitCode=1;async function loadCraftIosBuilder(){const explicit=process.env.CRAFT_IOS_SRC;try{if(explicit)return await import(pathToFileURL(explicit).href);return await import("craft-native/ios")}catch(error){throw Error("Craft iOS builder is unavailable. Install the current craft-native package or set CRAFT_IOS_SRC to packages/ios/src/index.ts in a Craft checkout.",{cause:error})}}const configPath=projectPath("config/mobile.ts");if(!existsSync(configPath))throw Error("Missing config/mobile.ts. Add an ios section before running `buddy build:ios`.");const configModule=await import(`${pathToFileURL(configPath).href}?t=${Date.now()}`),config=configModule.default.ios;validateIosMobileConfig(config);const output=resolveMobilePath(projectPath(),config.output)??storagePath("framework/mobile/ios"),webAssets=resolveMobilePath(projectPath(),config.webAssets),fallbackWebAssets=resolveMobilePath(projectPath(),config.fallbackWebAssets),craftConfig=toCraftIosConfig(config);craftConfig.appIconPath=resolveMobilePath(projectPath(),config.appIcon);const builder=await loadCraftIosBuilder();await builder.init({name:config.appName,bundleId:config.bundleId,teamId:config.teamId,output,config:craftConfig});await builder.build({output,htmlPath:webAssets??fallbackWebAssets,devServer:craftConfig.devServerURL,generateProject:process.env.STACKS_IOS_SKIP_XCODEGEN!=="1"});const sourceRevision=Bun.spawnSync(["git","rev-parse","HEAD"],{cwd:projectPath()}).stdout.toString().trim(),craftConfigPath=`${output}/craft.config.json`,generatedConfig=JSON.parse(readFileSync(craftConfigPath,"utf8"));writeFileSync(`${output}/stacks-mobile.json`,`${JSON.stringify({schemaVersion:"1.0.0",platform:"ios",sourceRevision,source:webAssets?{kind:"bundled",path:webAssets}:{kind:"remote",url:craftConfig.devServerURL,fallback:fallbackWebAssets?{kind:"bundled",path:fallbackWebAssets}:void 0},capabilities:config.capabilities??{},builder:resolveCraftBuilderProvenance(process.env.CRAFT_IOS_SRC),craft:generatedConfig},null,2)}
2
+ `);log.success(`Built the Craft iOS project in ${output}`);process.exitCode=0;
@@ -1,3 +1,15 @@
1
+ /**
2
+ * The project's lintable files: everything git tracks, plus everything git
3
+ * would let you add.
4
+ *
5
+ * `--others --exclude-standard` is what makes this correct rather than merely
6
+ * broader. Listing tracked files alone skips every file that has not been
7
+ * staged yet, which is precisely the set a new commit introduces: you write a
8
+ * file, `buddy lint` says the project is clean because it never opened it, you
9
+ * commit, and CI fails on the file you just linted. `--exclude-standard` keeps
10
+ * .gitignore honoured, so build output and dependencies stay out.
11
+ */
12
+ export declare function lintableFiles(cwd: string): string[];
1
13
  /**
2
14
  * Lint (optionally auto-fix) the project's tracked source via pickier's SDK.
3
15
  * Returns `{ ok }` rather than exiting, so callers keep control of the process.
package/dist/lint/lint.js CHANGED
@@ -1 +1 @@
1
- import process from"node:process";import{log}from"@stacksjs/cli";import{execSync}from"node:child_process";import{runFormat,runLint}from"pickier";const lintableFile=/\.(?:ts|js|json|md|yaml|yml)$/i,ignoredPath=/(?:^|\/)(?:node_modules|dist|pantry|storage\/framework\/cache|\.git|\.stx|\.stx-serve)(?:\/|$)/;function trackedFiles(cwd){try{return execSync("git ls-files -z",{cwd,encoding:"utf8"}).split("\x00").filter((file)=>lintableFile.test(file)&&!ignoredPath.test(file))}catch{return[]}}export async function lintProject(options={}){const cwd=options.cwd??process.cwd();log.info(options.fix?"Ensuring Code Style...":"Checking Code Style...");const files=trackedFiles(cwd);if(!files.length){log.success("Linted");return{ok:!0}}const ok=await runLint(files,{maxWarnings:9999,fix:options.fix})===0;if(ok)log.success("Linted");return{ok}}export function lintFix(options={}){return lintProject({...options,fix:!0})}export async function formatProject(options={}){const cwd=options.cwd??process.cwd(),files=trackedFiles(cwd);if(!files.length)return{ok:!0};return{ok:await runFormat(files,{write:options.write,check:options.check})===0}}
1
+ import process from"node:process";import{log}from"@stacksjs/cli";import{execSync}from"node:child_process";import{runFormat,runLint}from"pickier";const lintableFile=/\.(?:ts|js|json|md|yaml|yml)$/i,ignoredPath=/(?:^|\/)(?:node_modules|dist|pantry|storage\/framework\/cache|\.git|\.stx|\.stx-serve)(?:\/|$)/;export function lintableFiles(cwd){try{return execSync("git ls-files -z --cached --others --exclude-standard",{cwd,encoding:"utf8",stdio:["ignore","pipe","ignore"]}).split("\x00").filter((file)=>lintableFile.test(file)&&!ignoredPath.test(file))}catch{return[]}}export async function lintProject(options={}){const cwd=options.cwd??process.cwd();log.info(options.fix?"Ensuring Code Style...":"Checking Code Style...");const files=lintableFiles(cwd);if(!files.length){log.success("Linted");return{ok:!0}}const ok=await runLint(files,{maxWarnings:9999,fix:options.fix})===0;if(ok)log.success("Linted");return{ok}}export function lintFix(options={}){return lintProject({...options,fix:!0})}export async function formatProject(options={}){const cwd=options.cwd??process.cwd(),files=lintableFiles(cwd);if(!files.length)return{ok:!0};return{ok:await runFormat(files,{write:options.write,check:options.check})===0}}
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/actions",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.371",
5
+ "version": "0.70.376",
6
6
  "description": "The Stacks actions.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -59,34 +59,34 @@
59
59
  "prepublishOnly": "bun run build"
60
60
  },
61
61
  "dependencies": {
62
- "@stacksjs/config": "0.70.371",
62
+ "@stacksjs/config": "0.70.376",
63
63
  "@stacksjs/bumpx": "^0.2.6",
64
64
  "@stacksjs/bunpress": "^0.2.2",
65
65
  "@stacksjs/logsmith": "^0.2.3",
66
- "@stacksjs/registry": "0.70.371",
66
+ "@stacksjs/registry": "0.70.376",
67
67
  "@stacksjs/stx": "^0.2.176",
68
68
  "@stacksjs/ts-cloud": "^0.7.103",
69
69
  "@stacksjs/ts-md": "^0.1.1",
70
70
  "craft-native": ">=0.0.55"
71
71
  },
72
72
  "devDependencies": {
73
- "@stacksjs/api": "0.70.371",
74
- "@stacksjs/cli": "0.70.371",
75
- "@stacksjs/database": "0.70.371",
73
+ "@stacksjs/api": "0.70.376",
74
+ "@stacksjs/cli": "0.70.376",
75
+ "@stacksjs/database": "0.70.376",
76
76
  "@stacksjs/tlsx": "^0.13.2",
77
77
  "better-dx": "^0.2.17",
78
- "@stacksjs/dns": "0.70.371",
79
- "@stacksjs/enums": "0.70.371",
80
- "@stacksjs/env": "0.70.371",
81
- "@stacksjs/error-handling": "0.70.371",
82
- "@stacksjs/image": "0.70.371",
83
- "@stacksjs/logging": "0.70.371",
84
- "@stacksjs/path": "0.70.371",
85
- "@stacksjs/security": "0.70.371",
86
- "@stacksjs/storage": "0.70.371",
87
- "@stacksjs/strings": "0.70.371",
88
- "@stacksjs/utils": "0.70.371",
89
- "@stacksjs/validation": "0.70.371"
78
+ "@stacksjs/dns": "0.70.376",
79
+ "@stacksjs/enums": "0.70.376",
80
+ "@stacksjs/env": "0.70.376",
81
+ "@stacksjs/error-handling": "0.70.376",
82
+ "@stacksjs/image": "0.70.376",
83
+ "@stacksjs/logging": "0.70.376",
84
+ "@stacksjs/path": "0.70.376",
85
+ "@stacksjs/security": "0.70.376",
86
+ "@stacksjs/storage": "0.70.376",
87
+ "@stacksjs/strings": "0.70.376",
88
+ "@stacksjs/utils": "0.70.376",
89
+ "@stacksjs/validation": "0.70.376"
90
90
  },
91
91
  "peerDependencies": {
92
92
  "pickier": "^0.1.35"