@invarn/cibuild 1.3.19 → 1.4.1
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/dist/cli.cjs +1 -1
- package/dist/src/cli.js +10 -3
- package/dist/src/commands/build.d.ts.map +1 -1
- package/dist/src/commands/build.js +4 -2
- package/dist/src/commands/edit.js +2 -2
- package/dist/src/yaml/converter.d.ts.map +1 -1
- package/dist/src/yaml/converter.js +25 -1
- package/dist/src/yaml/converter.test.js +149 -0
- package/dist/src/yaml/parser.js +3 -3
- package/dist/src/yaml/platform-detector.d.ts +1 -1
- package/dist/src/yaml/platform-detector.js +2 -2
- package/dist/src/yaml/platform-detector.test.js +0 -19
- package/dist/src/yaml/steps/cache.d.ts +2 -0
- package/dist/src/yaml/steps/cache.d.ts.map +1 -1
- package/dist/src/yaml/steps/cache.js +84 -23
- package/dist/src/yaml/types.d.ts +3 -2
- package/dist/src/yaml/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/src/cli.js
CHANGED
|
@@ -180,7 +180,7 @@ async function handleInitCommand(opts = {}) {
|
|
|
180
180
|
];
|
|
181
181
|
const dependencies = [
|
|
182
182
|
...commonDependencies,
|
|
183
|
-
...(projectType === "android" ? androidDependencies : iosDependencies),
|
|
183
|
+
...(projectType === "android" ? androidDependencies : projectType === "kmm" ? [...androidDependencies, ...iosDependencies] : iosDependencies),
|
|
184
184
|
];
|
|
185
185
|
let allRequired = true;
|
|
186
186
|
let missingOptional = false;
|
|
@@ -318,8 +318,8 @@ async function handleInitCommand(opts = {}) {
|
|
|
318
318
|
try {
|
|
319
319
|
const yamlPipeline = loadYAMLPipeline(destPath);
|
|
320
320
|
const meta = yamlPipeline.meta?.["cibuild.io"];
|
|
321
|
-
if (meta?.platform === "ios" || meta?.platform === "android") {
|
|
322
|
-
importedPlatform = meta.platform;
|
|
321
|
+
if (meta?.platform === "ios" || meta?.platform === "android" || meta?.platform === "kmm") {
|
|
322
|
+
importedPlatform = meta.platform === "kmm" ? "ios" : meta.platform;
|
|
323
323
|
}
|
|
324
324
|
}
|
|
325
325
|
catch { /* ignore parse errors — default to macos-latest */ }
|
|
@@ -455,6 +455,7 @@ Usage:
|
|
|
455
455
|
ci run <path> [-w <name>] --skip-validation Skip validation, run with interactive prompts
|
|
456
456
|
ci validate <path> [-w <name>] Validate pipeline (alias for --validate-only)
|
|
457
457
|
ci detect-platform <path> [-w <name>] Detect platform from YAML pipeline
|
|
458
|
+
ci detect-project Detect project type (android, ios, kmm)
|
|
458
459
|
ci reset [--force] Remove all cibuild files and folders
|
|
459
460
|
ci edit <path> [-w <name>] View pipeline and edit step inputs
|
|
460
461
|
ci secrets add <var_name> <path> [-w <name>] Add a secret (prompted interactively)
|
|
@@ -502,6 +503,12 @@ Examples:
|
|
|
502
503
|
await import("./envman/cli.js");
|
|
503
504
|
return;
|
|
504
505
|
}
|
|
506
|
+
// Handle detect-project command
|
|
507
|
+
if (args[0] === "detect-project") {
|
|
508
|
+
const projectType = detectMobileProjectRoot(process.cwd());
|
|
509
|
+
console.log(projectType ?? "unknown");
|
|
510
|
+
process.exit(projectType ? 0 : 1);
|
|
511
|
+
}
|
|
505
512
|
// Handle init command
|
|
506
513
|
if (args[0] === "init") {
|
|
507
514
|
const importIdx = args.indexOf("--import");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../../src/commands/build.ts"],"names":[],"mappings":"AAk8BA,wBAAsB,kBAAkB,CACtC,uBAAuB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG,IAAI,EAC1E,OAAO,GAAE;IAAE,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAAC,cAAc,CAAC,EAAE,OAAO,CAAA;CAAO,GACvE,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
|
|
@@ -199,13 +199,15 @@ function generateAndroidPipeline(javaVersion = 17, setup = { keystore: false, ke
|
|
|
199
199
|
const releaseGradleTask = isAab ? `bundle${rv.buildType}` : rv.gradleTask;
|
|
200
200
|
const releaseArtifactLabel = isAab ? 'AAB' : 'APK';
|
|
201
201
|
const releaseArtifactExt = isAab ? '*.aab' : '*.apk';
|
|
202
|
+
const stack = cacheTechnology === "kmm" ? "macos-xcode-26.4" : "linux-docker-android-22.04";
|
|
203
|
+
const platformMeta = cacheTechnology === "kmm" ? "kmm" : "android";
|
|
202
204
|
return `format_version: '1'
|
|
203
205
|
|
|
204
206
|
meta:
|
|
205
207
|
cibuild.io:
|
|
206
|
-
stack:
|
|
208
|
+
stack: ${stack}
|
|
207
209
|
machine_type: standard
|
|
208
|
-
platform:
|
|
210
|
+
platform: ${platformMeta}
|
|
209
211
|
|
|
210
212
|
app:
|
|
211
213
|
envs:
|
|
@@ -293,8 +293,8 @@ async function handleAddRegisteredStep(pipelinePath, workflowName, _pipeline, st
|
|
|
293
293
|
function detectWorkflowPlatform(pipeline, workflowName) {
|
|
294
294
|
// 1. Explicit platform declaration in meta
|
|
295
295
|
const metaPlatform = pipeline.meta?.['cibuild.io']?.platform;
|
|
296
|
-
if (metaPlatform === 'ios' || metaPlatform === 'android')
|
|
297
|
-
return metaPlatform;
|
|
296
|
+
if (metaPlatform === 'ios' || metaPlatform === 'android' || metaPlatform === 'kmm')
|
|
297
|
+
return metaPlatform === 'kmm' ? 'ios' : metaPlatform;
|
|
298
298
|
// 2. Scan existing steps as fallback
|
|
299
299
|
const workflow = pipeline.workflows[workflowName];
|
|
300
300
|
if (!workflow)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../../src/yaml/converter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AAKzE,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAkCD;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"converter.d.ts","sourceRoot":"","sources":["../../../src/yaml/converter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAW,QAAQ,EAAE,MAAM,aAAa,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAA4B,MAAM,YAAY,CAAC;AAKzE,qBAAa,mBAAoB,SAAQ,KAAK;gBAChC,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,WAAW,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAkCD;;;;;;;GAOG;AACH,wBAAsB,wBAAwB,CAC5C,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,QAAQ,EAChB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACpB,OAAO,CAAC,gBAAgB,CAAC,CA0I3B;AAwGD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,MAAM,EAAE,CAEtE"}
|
|
@@ -60,11 +60,35 @@ export async function convertYAMLToPipelineDef(pipeline, config, workflowName, y
|
|
|
60
60
|
const platformInfo = detectPlatformInfo(pipeline, selectedWorkflowName);
|
|
61
61
|
// Step 3: Environment variable resolution
|
|
62
62
|
const envResolver = new EnvResolver(pipeline, selectedWorkflow, selectedWorkflowName, platformInfo.platform, platformInfo.stack, yamlFilePath);
|
|
63
|
+
// Step 3.5: Auto-inject cache steps if cachedBuild is enabled
|
|
64
|
+
const cibuildMeta = pipeline.meta?.['cibuild.io'];
|
|
65
|
+
const cachedBuild = cibuildMeta?.cachedBuild === true;
|
|
66
|
+
let workflowSteps = [...selectedWorkflow.steps];
|
|
67
|
+
if (cachedBuild && cibuildMeta?.platform) {
|
|
68
|
+
const platformToCacheTechnology = {
|
|
69
|
+
ios: 'ios',
|
|
70
|
+
android: 'gradle',
|
|
71
|
+
kmm: 'kmm',
|
|
72
|
+
};
|
|
73
|
+
const technology = platformToCacheTechnology[cibuildMeta.platform];
|
|
74
|
+
if (technology) {
|
|
75
|
+
// Remove any manually written cache-pull/cache-push steps
|
|
76
|
+
workflowSteps = workflowSteps.filter(step => {
|
|
77
|
+
const stepKey = Object.keys(step)[0];
|
|
78
|
+
const stepName = stepKey.split('@')[0];
|
|
79
|
+
return stepName !== 'cache-pull' && stepName !== 'cache-push';
|
|
80
|
+
});
|
|
81
|
+
const cachePullStep = { 'cache-pull@1.0.0': { inputs: { technology } } };
|
|
82
|
+
const cachePushStep = { 'cache-push@1.0.0': { inputs: { technology } } };
|
|
83
|
+
workflowSteps.unshift(cachePullStep);
|
|
84
|
+
workflowSteps.push(cachePushStep);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
63
87
|
// Step 4: Parse and convert steps
|
|
64
88
|
const steps = [];
|
|
65
89
|
const stepsOrder = [];
|
|
66
90
|
let stepCounter = 0;
|
|
67
|
-
for (const yamlStep of
|
|
91
|
+
for (const yamlStep of workflowSteps) {
|
|
68
92
|
// Parse step (sub-task 3.8)
|
|
69
93
|
const parsedStep = parseStep(yamlStep, stepCounter);
|
|
70
94
|
stepCounter++;
|
|
@@ -6,6 +6,7 @@ import { convertYAMLToPipelineDef, YAMLConversionError } from './converter.js';
|
|
|
6
6
|
import { clearRegistry, registerStep } from './steps/registry.js';
|
|
7
7
|
import { ScriptStepExecutor } from './steps/script.js';
|
|
8
8
|
import { CocoapodsInstallStepExecutor } from './steps/ios-deps.js';
|
|
9
|
+
import { CachePullStepExecutor, CachePushStepExecutor } from './steps/cache.js';
|
|
9
10
|
describe('convertYAMLToPipelineDef - Warning System', () => {
|
|
10
11
|
let mockConfig;
|
|
11
12
|
beforeEach(() => {
|
|
@@ -344,5 +345,153 @@ describe('convertYAMLToPipelineDef - Warning System', () => {
|
|
|
344
345
|
expect(result.skippedSteps).toBeGreaterThan(0);
|
|
345
346
|
});
|
|
346
347
|
});
|
|
348
|
+
describe('cachedBuild auto-injection', () => {
|
|
349
|
+
beforeEach(() => {
|
|
350
|
+
registerStep('cache-pull', new CachePullStepExecutor());
|
|
351
|
+
registerStep('cache-push', new CachePushStepExecutor());
|
|
352
|
+
});
|
|
353
|
+
test('should inject cache-pull and cache-push when cachedBuild is true', async () => {
|
|
354
|
+
const pipeline = {
|
|
355
|
+
format_version: '1',
|
|
356
|
+
meta: {
|
|
357
|
+
'cibuild.io': {
|
|
358
|
+
stack: 'macos-xcode-26.4',
|
|
359
|
+
platform: 'kmm',
|
|
360
|
+
cachedBuild: true,
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
workflows: {
|
|
364
|
+
build: {
|
|
365
|
+
steps: [
|
|
366
|
+
{ 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
|
|
367
|
+
],
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
|
|
372
|
+
expect(result.pipeline.steps.length).toBe(3);
|
|
373
|
+
expect(result.pipeline.steps[0].name).toBe('cache-pull');
|
|
374
|
+
expect(result.pipeline.steps[0].script).toContain('kmm-');
|
|
375
|
+
expect(result.pipeline.steps[1].name).toBe('script');
|
|
376
|
+
expect(result.pipeline.steps[2].name).toBe('cache-push');
|
|
377
|
+
expect(result.pipeline.steps[2].script).toContain('kmm-');
|
|
378
|
+
});
|
|
379
|
+
test('should not inject cache steps when cachedBuild is false', async () => {
|
|
380
|
+
const pipeline = {
|
|
381
|
+
format_version: '1',
|
|
382
|
+
meta: {
|
|
383
|
+
'cibuild.io': {
|
|
384
|
+
stack: 'macos-xcode-26.4',
|
|
385
|
+
platform: 'kmm',
|
|
386
|
+
cachedBuild: false,
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
workflows: {
|
|
390
|
+
build: {
|
|
391
|
+
steps: [
|
|
392
|
+
{ 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
|
|
398
|
+
expect(result.pipeline.steps.length).toBe(1);
|
|
399
|
+
expect(result.pipeline.steps[0].name).toBe('script');
|
|
400
|
+
});
|
|
401
|
+
test('should not inject cache steps when cachedBuild is absent', async () => {
|
|
402
|
+
const pipeline = {
|
|
403
|
+
format_version: '1',
|
|
404
|
+
meta: {
|
|
405
|
+
'cibuild.io': {
|
|
406
|
+
stack: 'macos-xcode-26.4',
|
|
407
|
+
platform: 'kmm',
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
workflows: {
|
|
411
|
+
build: {
|
|
412
|
+
steps: [
|
|
413
|
+
{ 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
|
|
414
|
+
],
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
|
|
419
|
+
expect(result.pipeline.steps.length).toBe(1);
|
|
420
|
+
});
|
|
421
|
+
test('should use correct technology for android platform', async () => {
|
|
422
|
+
const pipeline = {
|
|
423
|
+
format_version: '1',
|
|
424
|
+
meta: {
|
|
425
|
+
'cibuild.io': {
|
|
426
|
+
stack: 'linux-docker-android-22.04',
|
|
427
|
+
platform: 'android',
|
|
428
|
+
cachedBuild: true,
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
workflows: {
|
|
432
|
+
build: {
|
|
433
|
+
steps: [
|
|
434
|
+
{ 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
|
|
435
|
+
],
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
};
|
|
439
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
|
|
440
|
+
expect(result.pipeline.steps[0].script).toContain('gradle-');
|
|
441
|
+
expect(result.pipeline.steps[2].script).toContain('gradle-');
|
|
442
|
+
});
|
|
443
|
+
test('should use cocoapods technology for ios platform', async () => {
|
|
444
|
+
const pipeline = {
|
|
445
|
+
format_version: '1',
|
|
446
|
+
meta: {
|
|
447
|
+
'cibuild.io': {
|
|
448
|
+
stack: 'macos-xcode-26.4',
|
|
449
|
+
platform: 'ios',
|
|
450
|
+
cachedBuild: true,
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
workflows: {
|
|
454
|
+
build: {
|
|
455
|
+
steps: [
|
|
456
|
+
{ 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
|
|
457
|
+
],
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
|
|
462
|
+
expect(result.pipeline.steps[0].script).toContain('pods-');
|
|
463
|
+
expect(result.pipeline.steps[2].script).toContain('pods-');
|
|
464
|
+
});
|
|
465
|
+
test('should strip manual cache steps when cachedBuild is true', async () => {
|
|
466
|
+
const pipeline = {
|
|
467
|
+
format_version: '1',
|
|
468
|
+
meta: {
|
|
469
|
+
'cibuild.io': {
|
|
470
|
+
stack: 'macos-xcode-26.4',
|
|
471
|
+
platform: 'kmm',
|
|
472
|
+
cachedBuild: true,
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
workflows: {
|
|
476
|
+
build: {
|
|
477
|
+
steps: [
|
|
478
|
+
{ 'cache-pull@1.0.0': { inputs: { technology: 'gradle' } } },
|
|
479
|
+
{ 'script@1.0.0': { inputs: { content: 'echo "building"' } } },
|
|
480
|
+
{ 'cache-push@1.0.0': { inputs: { technology: 'gradle' } } },
|
|
481
|
+
],
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
const result = await convertYAMLToPipelineDef(pipeline, mockConfig, 'build');
|
|
486
|
+
// Should have 3 steps: auto-injected pull, script, auto-injected push
|
|
487
|
+
// The manual cache steps should be replaced, not duplicated
|
|
488
|
+
expect(result.pipeline.steps.length).toBe(3);
|
|
489
|
+
expect(result.pipeline.steps[0].name).toBe('cache-pull');
|
|
490
|
+
expect(result.pipeline.steps[0].script).toContain('kmm-'); // auto uses kmm, not gradle
|
|
491
|
+
expect(result.pipeline.steps[1].name).toBe('script');
|
|
492
|
+
expect(result.pipeline.steps[2].name).toBe('cache-push');
|
|
493
|
+
expect(result.pipeline.steps[2].script).toContain('kmm-');
|
|
494
|
+
});
|
|
495
|
+
});
|
|
347
496
|
});
|
|
348
497
|
//# sourceMappingURL=converter.test.js.map
|
package/dist/src/yaml/parser.js
CHANGED
|
@@ -97,7 +97,7 @@ function validateYAMLPipeline(parsed, filePath) {
|
|
|
97
97
|
}
|
|
98
98
|
// Validate platform if specified
|
|
99
99
|
if (parsed.meta.platform) {
|
|
100
|
-
const validPlatforms = ['macos', 'linux'
|
|
100
|
+
const validPlatforms = ['macos', 'linux'];
|
|
101
101
|
if (!validPlatforms.includes(parsed.meta.platform)) {
|
|
102
102
|
throw new YAMLValidationError(`Invalid platform '${parsed.meta.platform}' in ${filePath}. Must be one of: ${validPlatforms.join(', ')}`);
|
|
103
103
|
}
|
|
@@ -110,9 +110,9 @@ function validateYAMLPipeline(parsed, filePath) {
|
|
|
110
110
|
}
|
|
111
111
|
// Validate stack pattern if specified
|
|
112
112
|
if (cibuildMeta.stack && typeof cibuildMeta.stack === 'string') {
|
|
113
|
-
const stackPattern = /^(macos|linux
|
|
113
|
+
const stackPattern = /^(macos|linux)-/;
|
|
114
114
|
if (cibuildMeta.stack !== 'local' && !stackPattern.test(cibuildMeta.stack)) {
|
|
115
|
-
throw new YAMLValidationError(`Invalid stack '${cibuildMeta.stack}' in ${filePath}. Stack must be 'local' or start with 'macos-'
|
|
115
|
+
throw new YAMLValidationError(`Invalid stack '${cibuildMeta.stack}' in ${filePath}. Stack must be 'local' or start with 'macos-' or 'linux-'`);
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
// Validate machine_type if specified
|
|
@@ -20,7 +20,7 @@ export declare class PlatformDetector {
|
|
|
20
20
|
* Priority 2: meta.platform
|
|
21
21
|
* Priority 3: auto-detect from workflow steps
|
|
22
22
|
*
|
|
23
|
-
* @returns Detected platform ('macos'
|
|
23
|
+
* @returns Detected platform ('macos' or 'linux')
|
|
24
24
|
* @throws PlatformDetectionError if detection fails or mixed platforms detected
|
|
25
25
|
*/
|
|
26
26
|
getPlatform(): Platform;
|
|
@@ -29,7 +29,7 @@ export class PlatformDetector {
|
|
|
29
29
|
* Priority 2: meta.platform
|
|
30
30
|
* Priority 3: auto-detect from workflow steps
|
|
31
31
|
*
|
|
32
|
-
* @returns Detected platform ('macos'
|
|
32
|
+
* @returns Detected platform ('macos' or 'linux')
|
|
33
33
|
* @throws PlatformDetectionError if detection fails or mixed platforms detected
|
|
34
34
|
*/
|
|
35
35
|
getPlatform() {
|
|
@@ -84,7 +84,7 @@ export class PlatformDetector {
|
|
|
84
84
|
return null;
|
|
85
85
|
}
|
|
86
86
|
const platformPrefix = parts[0];
|
|
87
|
-
if (platformPrefix === 'macos' || platformPrefix === 'linux'
|
|
87
|
+
if (platformPrefix === 'macos' || platformPrefix === 'linux') {
|
|
88
88
|
return platformPrefix;
|
|
89
89
|
}
|
|
90
90
|
return null;
|
|
@@ -45,25 +45,6 @@ describe('PlatformDetector', () => {
|
|
|
45
45
|
expect(detector.getStack()).toBe('linux-docker-android-22.04');
|
|
46
46
|
expect(detector.getMachineType()).toBe('standard');
|
|
47
47
|
});
|
|
48
|
-
test('should detect windows from stack', () => {
|
|
49
|
-
const pipeline = {
|
|
50
|
-
format_version: '4',
|
|
51
|
-
meta: {
|
|
52
|
-
'cibuild.io': {
|
|
53
|
-
stack: 'windows-server-2022',
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
workflows: {
|
|
57
|
-
primary: {
|
|
58
|
-
steps: [{ 'script@1.0.0': { inputs: { content: 'echo "test"' } } }],
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
};
|
|
62
|
-
const detector = new PlatformDetector(pipeline, pipeline.workflows.primary, 'primary');
|
|
63
|
-
expect(detector.getPlatform()).toBe('windows');
|
|
64
|
-
expect(detector.getStack()).toBe('windows-server-2022');
|
|
65
|
-
expect(detector.getMachineType()).toBeNull();
|
|
66
|
-
});
|
|
67
48
|
});
|
|
68
49
|
describe('Priority 2: Use meta.platform field', () => {
|
|
69
50
|
test('should use meta.platform when stack not specified', () => {
|
|
@@ -12,6 +12,8 @@ export interface CachePreset {
|
|
|
12
12
|
lockfile: string;
|
|
13
13
|
keyPrefix: string;
|
|
14
14
|
paths: string[];
|
|
15
|
+
/** Optional fallback preset name: if primary lockfile not found, use this preset's lockfile/key/paths */
|
|
16
|
+
fallback?: string;
|
|
15
17
|
}
|
|
16
18
|
export declare const CACHE_PRESETS: Record<string, CachePreset>;
|
|
17
19
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/cache.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAExD;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAUrD,CAAC;AAoBF;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAoGzF,iBAAiB;CA2FhC;AAED;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,gBAAgB;IACnD,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC;YAwJzF,iBAAiB;CA6FhC"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { BaseStepExecutor } from './base.js';
|
|
5
5
|
export const CACHE_PRESETS = {
|
|
6
|
+
ios: { lockfile: 'Podfile.lock', keyPrefix: 'pods', paths: ['Pods'], fallback: 'spm' },
|
|
6
7
|
cocoapods: { lockfile: 'Podfile.lock', keyPrefix: 'pods', paths: ['Pods'] },
|
|
7
8
|
carthage: { lockfile: 'Cartfile.resolved', keyPrefix: 'carthage', paths: ['Carthage'] },
|
|
8
9
|
spm: { lockfile: 'Package.resolved', keyPrefix: 'spm', paths: ['~/Library/Developer/Xcode/DerivedData/*/SourcePackages'] },
|
|
@@ -12,6 +13,24 @@ export const CACHE_PRESETS = {
|
|
|
12
13
|
yarn: { lockfile: 'yarn.lock', keyPrefix: 'yarn', paths: ['node_modules'] },
|
|
13
14
|
dart: { lockfile: 'pubspec.lock', keyPrefix: 'dart', paths: ['.dart_tool', '.pub-cache'] },
|
|
14
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Resolves a preset with its fallback chain into a flat list of
|
|
18
|
+
* {lockfile, keyPrefix, paths} entries to try in order at runtime.
|
|
19
|
+
*/
|
|
20
|
+
function resolvePresetChain(technology) {
|
|
21
|
+
const chain = [];
|
|
22
|
+
let current = technology;
|
|
23
|
+
const visited = new Set();
|
|
24
|
+
while (current && !visited.has(current)) {
|
|
25
|
+
visited.add(current);
|
|
26
|
+
const preset = CACHE_PRESETS[current];
|
|
27
|
+
if (!preset)
|
|
28
|
+
break;
|
|
29
|
+
chain.push(preset);
|
|
30
|
+
current = preset.fallback;
|
|
31
|
+
}
|
|
32
|
+
return chain;
|
|
33
|
+
}
|
|
15
34
|
/**
|
|
16
35
|
* Cache pull step executor
|
|
17
36
|
* Restores cached files from cache directory
|
|
@@ -97,8 +116,8 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
97
116
|
}
|
|
98
117
|
async executeWithPreset(technology, inputs, config) {
|
|
99
118
|
const stepName = 'cache-pull';
|
|
100
|
-
const
|
|
101
|
-
if (
|
|
119
|
+
const chain = resolvePresetChain(technology);
|
|
120
|
+
if (chain.length === 0) {
|
|
102
121
|
const supported = Object.keys(CACHE_PRESETS).join(', ');
|
|
103
122
|
throw new Error(`Unknown cache technology '${technology}'. Supported: ${supported}`);
|
|
104
123
|
}
|
|
@@ -108,17 +127,39 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
108
127
|
commands.push(`echo "Restoring ${technology} cache..."`);
|
|
109
128
|
commands.push('');
|
|
110
129
|
// Compute cache key from lockfile checksum at runtime
|
|
130
|
+
// If technology has a fallback chain, try each lockfile in order
|
|
111
131
|
commands.push(`CACHE_DIR="${this.escapeBash(config.paths.cacheDir)}"`);
|
|
112
132
|
commands.push('mkdir -p "$CACHE_DIR"');
|
|
113
133
|
commands.push('');
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
134
|
+
if (chain.length === 1) {
|
|
135
|
+
const preset = chain[0];
|
|
136
|
+
commands.push(`LOCKFILE="${this.escapeBash(preset.lockfile)}"`);
|
|
137
|
+
commands.push('if [ -f "$LOCKFILE" ]; then');
|
|
138
|
+
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
139
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
140
|
+
commands.push('else');
|
|
141
|
+
commands.push(` echo "Warning: $LOCKFILE not found, using fallback cache key"`);
|
|
142
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-no-lockfile"`);
|
|
143
|
+
commands.push('fi');
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
// Fallback chain: try each lockfile in order, use the first one found
|
|
147
|
+
for (let i = 0; i < chain.length; i++) {
|
|
148
|
+
const preset = chain[i];
|
|
149
|
+
const cond = i === 0 ? 'if' : 'elif';
|
|
150
|
+
commands.push(`${cond} [ -f "${this.escapeBash(preset.lockfile)}" ]; then`);
|
|
151
|
+
commands.push(` LOCKFILE="${this.escapeBash(preset.lockfile)}"`);
|
|
152
|
+
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
153
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
154
|
+
if (isDebugMode) {
|
|
155
|
+
commands.push(` echo "Detected: ${this.escapeBash(preset.keyPrefix)} (lockfile: $LOCKFILE)"`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
commands.push('else');
|
|
159
|
+
commands.push(` echo "Warning: no lockfile found, using fallback cache key"`);
|
|
160
|
+
commands.push(` CACHE_KEY="${this.escapeBash(chain[0].keyPrefix)}-no-lockfile"`);
|
|
161
|
+
commands.push('fi');
|
|
162
|
+
}
|
|
122
163
|
commands.push('');
|
|
123
164
|
if (isDebugMode) {
|
|
124
165
|
commands.push('echo "Lockfile: $LOCKFILE"');
|
|
@@ -135,7 +176,8 @@ export class CachePullStepExecutor extends BaseStepExecutor {
|
|
|
135
176
|
commands.push(' echo "Cache restored successfully"');
|
|
136
177
|
if (isDebugMode) {
|
|
137
178
|
commands.push(' echo "Restored paths:"');
|
|
138
|
-
|
|
179
|
+
const allPaths = [...new Set(chain.flatMap(p => p.paths))];
|
|
180
|
+
for (const p of allPaths) {
|
|
139
181
|
commands.push(` EXPANDED="${this.escapeBash(p)}"`);
|
|
140
182
|
commands.push(' EXPANDED="${EXPANDED/#~/${CIBUILD_USER_HOME:-$HOME}}"');
|
|
141
183
|
commands.push(' if [ -e "$EXPANDED" ]; then');
|
|
@@ -285,8 +327,8 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
285
327
|
}
|
|
286
328
|
async executeWithPreset(technology, inputs, config) {
|
|
287
329
|
const stepName = 'cache-push';
|
|
288
|
-
const
|
|
289
|
-
if (
|
|
330
|
+
const chain = resolvePresetChain(technology);
|
|
331
|
+
if (chain.length === 0) {
|
|
290
332
|
const supported = Object.keys(CACHE_PRESETS).join(', ');
|
|
291
333
|
throw new Error(`Unknown cache technology '${technology}'. Supported: ${supported}`);
|
|
292
334
|
}
|
|
@@ -299,14 +341,31 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
299
341
|
commands.push(`CACHE_DIR="${this.escapeBash(config.paths.cacheDir)}"`);
|
|
300
342
|
commands.push('mkdir -p "$CACHE_DIR"');
|
|
301
343
|
commands.push('');
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
344
|
+
if (chain.length === 1) {
|
|
345
|
+
const preset = chain[0];
|
|
346
|
+
commands.push(`LOCKFILE="${this.escapeBash(preset.lockfile)}"`);
|
|
347
|
+
commands.push('if [ -f "$LOCKFILE" ]; then');
|
|
348
|
+
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
349
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
350
|
+
commands.push('else');
|
|
351
|
+
commands.push(` echo "Warning: $LOCKFILE not found, using fallback cache key"`);
|
|
352
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-no-lockfile"`);
|
|
353
|
+
commands.push('fi');
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
for (let i = 0; i < chain.length; i++) {
|
|
357
|
+
const preset = chain[i];
|
|
358
|
+
const cond = i === 0 ? 'if' : 'elif';
|
|
359
|
+
commands.push(`${cond} [ -f "${this.escapeBash(preset.lockfile)}" ]; then`);
|
|
360
|
+
commands.push(` LOCKFILE="${this.escapeBash(preset.lockfile)}"`);
|
|
361
|
+
commands.push(` CHECKSUM=$(shasum -a 256 "$LOCKFILE" | cut -d ' ' -f1 | head -c 16)`);
|
|
362
|
+
commands.push(` CACHE_KEY="${this.escapeBash(preset.keyPrefix)}-\${CHECKSUM}"`);
|
|
363
|
+
}
|
|
364
|
+
commands.push('else');
|
|
365
|
+
commands.push(` echo "Warning: no lockfile found, using fallback cache key"`);
|
|
366
|
+
commands.push(` CACHE_KEY="${this.escapeBash(chain[0].keyPrefix)}-no-lockfile"`);
|
|
367
|
+
commands.push('fi');
|
|
368
|
+
}
|
|
310
369
|
commands.push('');
|
|
311
370
|
if (isDebugMode) {
|
|
312
371
|
commands.push('echo "Lockfile: $LOCKFILE"');
|
|
@@ -314,10 +373,12 @@ export class CachePushStepExecutor extends BaseStepExecutor {
|
|
|
314
373
|
}
|
|
315
374
|
commands.push('CACHE_FILE="$CACHE_DIR/$CACHE_KEY.tar.zst"');
|
|
316
375
|
commands.push('');
|
|
317
|
-
// Collect paths to cache
|
|
376
|
+
// Collect paths to cache — use all paths from all presets in the chain,
|
|
377
|
+
// only archiving the ones that actually exist on disk
|
|
318
378
|
commands.push('# Collect paths to cache');
|
|
319
379
|
commands.push('PATHS_TO_CACHE=()');
|
|
320
|
-
|
|
380
|
+
const allPaths = [...new Set(chain.flatMap(p => p.paths))];
|
|
381
|
+
for (const p of allPaths) {
|
|
321
382
|
commands.push(`EXPANDED="${this.escapeBash(p)}"`);
|
|
322
383
|
commands.push('EXPANDED="${EXPANDED/#~/${CIBUILD_USER_HOME:-$HOME}}"');
|
|
323
384
|
commands.push('if [ -e "$EXPANDED" ]; then');
|
package/dist/src/yaml/types.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* TypeScript interfaces for Bitrise-compatible YAML pipeline structure
|
|
3
3
|
*/
|
|
4
|
-
export type Platform = 'macos' | 'linux'
|
|
4
|
+
export type Platform = 'macos' | 'linux';
|
|
5
5
|
export type MachineType = 'standard' | 'performance';
|
|
6
6
|
export interface YAMLEnvVar {
|
|
7
7
|
[key: string]: string;
|
|
@@ -10,7 +10,8 @@ export interface YAMLCIBuildMeta {
|
|
|
10
10
|
stack?: string;
|
|
11
11
|
machine_type?: MachineType;
|
|
12
12
|
machine_type_id?: string;
|
|
13
|
-
platform?: 'ios' | 'android';
|
|
13
|
+
platform?: 'ios' | 'android' | 'kmm';
|
|
14
|
+
cachedBuild?: boolean;
|
|
14
15
|
}
|
|
15
16
|
export interface YAMLMeta {
|
|
16
17
|
'cibuild.io'?: YAMLCIBuildMeta;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/yaml/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/yaml/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;AACzC,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,aAAa,CAAC;AAGrD,MAAM,WAAW,UAAU;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;CACvB;AAGD,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,YAAY,CAAC,EAAE,WAAW,CAAC;IAC3B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;IACrC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,YAAY,CAAC,EAAE,eAAe,CAAC;IAC/B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB;AAGD,MAAM,WAAW,cAAc;IAC7B,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACpB;AAGD,MAAM,WAAW,QAAQ;IACvB,CAAC,mBAAmB,EAAE,MAAM,GAAG;QAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,cAAc,CAAC;QACxB,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,aAAa,CAAC,EAAE,OAAO,CAAC;QACxB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC;CACH;AAGD,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC;IACpB,KAAK,EAAE,QAAQ,EAAE,CAAC;CACnB;AAGD,MAAM,WAAW,OAAO;IACtB,IAAI,CAAC,EAAE,UAAU,EAAE,CAAC;CACrB;AAGD,MAAM,WAAW,WAAW;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,YAAY;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,SAAS,EAAE;QAAE,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAAA;KAAE,CAAC;IAC5C,WAAW,CAAC,EAAE,WAAW,EAAE,CAAC;CAC7B;AAGD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAGD,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,QAAQ,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,WAAW,CAAC;CAC3B"}
|