@pushpalsdev/cli 1.0.82 → 1.0.83
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/package.json
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* Used by both the host Worker (direct mode) and the Docker job runner.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { existsSync, readFileSync, rmSync, unlinkSync } from "fs";
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, unlinkSync } from "fs";
|
|
7
|
+
import { tmpdir } from "os";
|
|
7
8
|
import { resolve } from "path";
|
|
8
9
|
import {
|
|
9
10
|
deriveAutonomyComponentArea,
|
|
@@ -556,6 +557,7 @@ async function runValidationCommand(
|
|
|
556
557
|
const startedAt = Date.now();
|
|
557
558
|
const proc = Bun.spawn(argv, {
|
|
558
559
|
cwd: repo,
|
|
560
|
+
env: buildValidationCommandEnv(repo),
|
|
559
561
|
stdout: "pipe",
|
|
560
562
|
stderr: "pipe",
|
|
561
563
|
});
|
|
@@ -590,6 +592,38 @@ async function runValidationCommand(
|
|
|
590
592
|
};
|
|
591
593
|
}
|
|
592
594
|
|
|
595
|
+
function buildValidationCommandEnv(repo: string): Record<string, string> {
|
|
596
|
+
const homeDir = resolve(tmpdir(), "pushpals-validation-home");
|
|
597
|
+
const cacheDir = resolve(tmpdir(), "pushpals-validation-cache");
|
|
598
|
+
const expoDir = resolve(tmpdir(), "pushpals-validation-expo");
|
|
599
|
+
for (const dir of [homeDir, cacheDir, expoDir]) {
|
|
600
|
+
try {
|
|
601
|
+
mkdirSync(dir, { recursive: true });
|
|
602
|
+
} catch {
|
|
603
|
+
// Keep validation best-effort; the command output will expose any real env blocker.
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
const env: Record<string, string> = {};
|
|
607
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
608
|
+
if (typeof value === "string") env[key] = value;
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
...env,
|
|
612
|
+
HOME: homeDir,
|
|
613
|
+
USERPROFILE: homeDir,
|
|
614
|
+
XDG_CACHE_HOME: cacheDir,
|
|
615
|
+
npm_config_cache: resolve(cacheDir, "npm"),
|
|
616
|
+
EXPO_HOME: expoDir,
|
|
617
|
+
EXPO_NO_TELEMETRY: process.env.EXPO_NO_TELEMETRY ?? "1",
|
|
618
|
+
EXPO_NO_INTERACTIVE: process.env.EXPO_NO_INTERACTIVE ?? "1",
|
|
619
|
+
CI: process.env.CI ?? "1",
|
|
620
|
+
BROWSER: process.env.BROWSER ?? "none",
|
|
621
|
+
EXPO_DEV_SERVER_PORT: process.env.EXPO_DEV_SERVER_PORT ?? "19006",
|
|
622
|
+
RCT_METRO_PORT: process.env.RCT_METRO_PORT ?? "19006",
|
|
623
|
+
PUSHPALS_VALIDATION_REPO: repo,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
|
|
593
627
|
interface ToolAvailabilityResult {
|
|
594
628
|
requirement: ToolRequirement;
|
|
595
629
|
ok: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from "fs";
|
|
2
|
-
import { join, normalize } from "path";
|
|
2
|
+
import { isAbsolute, join, normalize } from "path";
|
|
3
3
|
|
|
4
4
|
export type ToolchainEnvironmentSource =
|
|
5
5
|
| "devcontainer"
|
|
@@ -27,6 +27,7 @@ export interface BuildToolchainPlanOptions {
|
|
|
27
27
|
repoRoot: string;
|
|
28
28
|
validationCommands: string[];
|
|
29
29
|
maxNativeScanEntries?: number;
|
|
30
|
+
maxScriptScanChars?: number;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const SHELL_CONTROL_TOKENS = new Set(["|", "||", "&", "&&", ";", ">", ">>", "<", "<<"]);
|
|
@@ -144,7 +145,14 @@ export function buildToolchainPlan(options: BuildToolchainPlanOptions): Toolchai
|
|
|
144
145
|
const nativeSignals = detectNativeSignals(repoRoot, options.maxNativeScanEntries ?? 1_000);
|
|
145
146
|
const requirements: ToolRequirement[] = [];
|
|
146
147
|
for (const command of options.validationCommands) {
|
|
147
|
-
requirements.push(
|
|
148
|
+
requirements.push(
|
|
149
|
+
...inferToolRequirementsForValidationCommand(
|
|
150
|
+
repoRoot,
|
|
151
|
+
command,
|
|
152
|
+
nativeSignals,
|
|
153
|
+
options.maxScriptScanChars ?? 64_000,
|
|
154
|
+
),
|
|
155
|
+
);
|
|
148
156
|
}
|
|
149
157
|
return {
|
|
150
158
|
requirements: dedupeToolRequirements(requirements),
|
|
@@ -156,6 +164,7 @@ export function inferToolRequirementsForValidationCommand(
|
|
|
156
164
|
repoRoot: string,
|
|
157
165
|
command: string,
|
|
158
166
|
nativeSignals: NativeSignals = detectNativeSignals(repoRoot),
|
|
167
|
+
maxScriptScanChars = 64_000,
|
|
159
168
|
): ToolRequirement[] {
|
|
160
169
|
const tokens = tokenizeToolchainCommand(command);
|
|
161
170
|
if (!tokens) return [];
|
|
@@ -177,7 +186,18 @@ export function inferToolRequirementsForValidationCommand(
|
|
|
177
186
|
|
|
178
187
|
const script = resolvePackageScript(repoRoot, tokens);
|
|
179
188
|
if (script) {
|
|
180
|
-
addScriptRequirements(
|
|
189
|
+
addScriptRequirements(
|
|
190
|
+
requirements,
|
|
191
|
+
repoRoot,
|
|
192
|
+
script.scriptCwd,
|
|
193
|
+
script.script,
|
|
194
|
+
script.detectedFrom,
|
|
195
|
+
command,
|
|
196
|
+
{
|
|
197
|
+
maxScriptScanChars,
|
|
198
|
+
depth: 0,
|
|
199
|
+
},
|
|
200
|
+
);
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
if (usesNativeBuildCommand(tokens)) {
|
|
@@ -257,17 +277,34 @@ function addNodeBackedCliRequirement(
|
|
|
257
277
|
|
|
258
278
|
function addScriptRequirements(
|
|
259
279
|
requirements: ToolRequirement[],
|
|
280
|
+
repoRoot: string,
|
|
281
|
+
scriptCwd: string,
|
|
260
282
|
script: string,
|
|
261
283
|
detectedFrom: string,
|
|
262
284
|
command: string,
|
|
285
|
+
options: { maxScriptScanChars: number; depth: number },
|
|
263
286
|
): void {
|
|
264
287
|
const tokens = tokenizeToolchainCommand(script) ?? script.split(/\s+/).filter(Boolean);
|
|
265
288
|
const first = normalizeToolToken(tokens[0] ?? "");
|
|
266
|
-
|
|
289
|
+
// Package-manager scripts resolve Node CLIs from local node_modules/.bin. Requiring a
|
|
290
|
+
// global expo/vite/tsc binary creates false environment blockers for normal JS repos.
|
|
291
|
+
if (!NODE_BACKED_CLI_NAMES.has(first)) {
|
|
292
|
+
addDirectExecutableRequirement(requirements, first, command);
|
|
293
|
+
}
|
|
267
294
|
addNodeBackedCliRequirement(requirements, first, detectedFrom, command);
|
|
268
295
|
for (const token of tokens) {
|
|
269
296
|
addNodeBackedCliRequirement(requirements, normalizeToolToken(token), detectedFrom, command);
|
|
270
297
|
}
|
|
298
|
+
for (const scriptPath of inferReferencedScriptPaths(repoRoot, scriptCwd, tokens)) {
|
|
299
|
+
const scanned = scanScriptFileForToolRequirements(
|
|
300
|
+
requirements,
|
|
301
|
+
repoRoot,
|
|
302
|
+
scriptPath,
|
|
303
|
+
command,
|
|
304
|
+
options,
|
|
305
|
+
);
|
|
306
|
+
if (scanned) continue;
|
|
307
|
+
}
|
|
271
308
|
if (/\bnode\b/.test(script)) {
|
|
272
309
|
requirements.push({
|
|
273
310
|
tool: "node",
|
|
@@ -288,6 +325,50 @@ function addScriptRequirements(
|
|
|
288
325
|
}
|
|
289
326
|
}
|
|
290
327
|
|
|
328
|
+
function scanScriptFileForToolRequirements(
|
|
329
|
+
requirements: ToolRequirement[],
|
|
330
|
+
repoRoot: string,
|
|
331
|
+
scriptPath: string,
|
|
332
|
+
command: string,
|
|
333
|
+
options: { maxScriptScanChars: number; depth: number },
|
|
334
|
+
): boolean {
|
|
335
|
+
if (options.depth > 2 || !existsSync(scriptPath)) return false;
|
|
336
|
+
let text = "";
|
|
337
|
+
try {
|
|
338
|
+
const stats = statSync(scriptPath);
|
|
339
|
+
if (!stats.isFile() || stats.size > options.maxScriptScanChars) return false;
|
|
340
|
+
text = readFileSync(scriptPath, "utf8");
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
const detectedFrom = `${repoRelativePath(repoRoot, scriptPath)} referenced by validation command "${command}"`;
|
|
345
|
+
for (const cliName of NODE_BACKED_CLI_NAMES) {
|
|
346
|
+
const pattern = new RegExp(`(?:^|[^A-Za-z0-9_-])${escapeRegExp(cliName)}(?:$|[^A-Za-z0-9_-])`);
|
|
347
|
+
if (pattern.test(text)) {
|
|
348
|
+
addNodeBackedCliRequirement(requirements, cliName, detectedFrom, command);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (/\bnode\b/.test(text)) {
|
|
352
|
+
requirements.push({
|
|
353
|
+
tool: "node",
|
|
354
|
+
candidates: ["node"],
|
|
355
|
+
reason: "referenced validation script invokes node directly",
|
|
356
|
+
detectedFrom,
|
|
357
|
+
requiredFor: [command],
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
if (/\bbun\b/.test(text)) {
|
|
361
|
+
requirements.push({
|
|
362
|
+
tool: "bun",
|
|
363
|
+
candidates: ["bun"],
|
|
364
|
+
reason: "referenced validation script invokes bun",
|
|
365
|
+
detectedFrom,
|
|
366
|
+
requiredFor: [command],
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
return true;
|
|
370
|
+
}
|
|
371
|
+
|
|
291
372
|
function resolveBunSubcommand(tokens: string[]): { kind: "run" | "x"; value: string } | null {
|
|
292
373
|
if (normalizeToolToken(tokens[0] ?? "") !== "bun") return null;
|
|
293
374
|
let index = 1;
|
|
@@ -313,7 +394,7 @@ function resolveBunSubcommand(tokens: string[]): { kind: "run" | "x"; value: str
|
|
|
313
394
|
function resolvePackageScript(
|
|
314
395
|
repoRoot: string,
|
|
315
396
|
tokens: string[],
|
|
316
|
-
): { script: string; detectedFrom: string } | null {
|
|
397
|
+
): { script: string; scriptCwd: string; detectedFrom: string } | null {
|
|
317
398
|
const first = normalizeToolToken(tokens[0] ?? "");
|
|
318
399
|
let cwd = repoRoot;
|
|
319
400
|
let scriptName = "";
|
|
@@ -379,6 +460,7 @@ function resolvePackageScript(
|
|
|
379
460
|
if (typeof script !== "string" || !script.trim()) return null;
|
|
380
461
|
return {
|
|
381
462
|
script,
|
|
463
|
+
scriptCwd: cwd,
|
|
382
464
|
detectedFrom: `${repoRelativePath(repoRoot, packagePath)} script "${scriptName}"`,
|
|
383
465
|
};
|
|
384
466
|
} catch {
|
|
@@ -386,6 +468,33 @@ function resolvePackageScript(
|
|
|
386
468
|
}
|
|
387
469
|
}
|
|
388
470
|
|
|
471
|
+
function inferReferencedScriptPaths(repoRoot: string, scriptCwd: string, tokens: string[]): string[] {
|
|
472
|
+
const out: string[] = [];
|
|
473
|
+
const seen = new Set<string>();
|
|
474
|
+
for (const token of tokens) {
|
|
475
|
+
const normalized = normalizeReferencedScriptToken(token);
|
|
476
|
+
if (!normalized) continue;
|
|
477
|
+
const resolved = isAbsolute(normalized) ? normalized : join(scriptCwd, normalized);
|
|
478
|
+
const key = normalize(resolved);
|
|
479
|
+
if (seen.has(key)) continue;
|
|
480
|
+
seen.add(key);
|
|
481
|
+
out.push(resolved);
|
|
482
|
+
}
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function normalizeReferencedScriptToken(token: string): string | null {
|
|
487
|
+
let normalized = token.replace(/\\/g, "/");
|
|
488
|
+
if (normalized.startsWith("-")) {
|
|
489
|
+
const equalsIndex = normalized.indexOf("=");
|
|
490
|
+
if (equalsIndex === -1) return null;
|
|
491
|
+
normalized = normalized.slice(equalsIndex + 1);
|
|
492
|
+
}
|
|
493
|
+
if (!/\.(cjs|cts|js|jsx|mjs|mts|ts|tsx)$/i.test(normalized)) return null;
|
|
494
|
+
if (normalized.includes("://")) return null;
|
|
495
|
+
return normalized;
|
|
496
|
+
}
|
|
497
|
+
|
|
389
498
|
function detectToolchainEnvironmentSource(repoRoot: string): ToolchainEnvironmentSource {
|
|
390
499
|
if (existsSync(join(repoRoot, ".devcontainer", "devcontainer.json"))) return "devcontainer";
|
|
391
500
|
if (existsSync(join(repoRoot, "devcontainer.json"))) return "devcontainer";
|
|
@@ -501,6 +610,10 @@ function normalizeToolToken(token: string): string {
|
|
|
501
610
|
return normalizedToken.toLowerCase().replace(/\.(cmd|exe|ps1)$/i, "");
|
|
502
611
|
}
|
|
503
612
|
|
|
613
|
+
function escapeRegExp(value: string): string {
|
|
614
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
615
|
+
}
|
|
616
|
+
|
|
504
617
|
function repoRelativePath(repoRoot: string, pathValue: string): string {
|
|
505
618
|
const root = normalize(repoRoot).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
506
619
|
const path = normalize(pathValue).replace(/\\/g, "/");
|