@promptframe/cli 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -2
- package/dist/index.js +534 -70
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -20,6 +20,20 @@ const SECURITY_EVALUATOR_MODE = 'ast';
|
|
|
20
20
|
const LOCKFILE_EVIDENCE_SCHEMA_VERSION = 'promptframe.lockfile-evidence.v0.1.0';
|
|
21
21
|
const LOCKFILE_EVIDENCE_FILE_NAME = 'promptframe-lockfile-evidence.json';
|
|
22
22
|
const PROMPTFRAME_WORKSPACE_SCHEMA_VERSION = 'promptframe-workspace.v0.1.0';
|
|
23
|
+
const PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION = 'promptframe-project-context.v0.1.0';
|
|
24
|
+
const PROMPTFRAME_PROJECT_CONTEXT_FILE_NAME = '.promptframerc';
|
|
25
|
+
const PROJECT_CONTEXT_FORBIDDEN_KEY_NAMES = new Set([
|
|
26
|
+
'apikey',
|
|
27
|
+
'auth0subject',
|
|
28
|
+
'authorization',
|
|
29
|
+
'citokensecret',
|
|
30
|
+
'clientsecret',
|
|
31
|
+
'cookie',
|
|
32
|
+
'oauthcode',
|
|
33
|
+
'password',
|
|
34
|
+
'token',
|
|
35
|
+
'tokensecret',
|
|
36
|
+
]);
|
|
23
37
|
const PACKAGE_MANAGER_LOCKFILE_NAMES = [
|
|
24
38
|
'package-lock.json',
|
|
25
39
|
'npm-shrinkwrap.json',
|
|
@@ -84,12 +98,19 @@ async function run(name, argv) {
|
|
|
84
98
|
case 'discovery':
|
|
85
99
|
await discovery(argv);
|
|
86
100
|
break;
|
|
101
|
+
case 'init':
|
|
102
|
+
await initProjectContext(argv);
|
|
103
|
+
break;
|
|
87
104
|
case 'project':
|
|
88
105
|
await project(argv);
|
|
89
106
|
break;
|
|
90
107
|
case 'projects':
|
|
91
108
|
await project(['list', ...argv]);
|
|
92
109
|
break;
|
|
110
|
+
case 'component':
|
|
111
|
+
case 'components':
|
|
112
|
+
await component(argv);
|
|
113
|
+
break;
|
|
93
114
|
case 'ci-token':
|
|
94
115
|
case 'token':
|
|
95
116
|
await ciToken(argv);
|
|
@@ -406,14 +427,7 @@ function evaluateDirectoryDependencyPolicy(dir) {
|
|
|
406
427
|
});
|
|
407
428
|
}
|
|
408
429
|
function hasPackageManagerLockfile(dir) {
|
|
409
|
-
return
|
|
410
|
-
'pnpm-lock.yaml',
|
|
411
|
-
'package-lock.json',
|
|
412
|
-
'npm-shrinkwrap.json',
|
|
413
|
-
'yarn.lock',
|
|
414
|
-
'bun.lock',
|
|
415
|
-
'bun.lockb',
|
|
416
|
-
].some((fileName) => existsSync(join(dir, fileName)));
|
|
430
|
+
return collectPackageManagerLockfileEvidence(dir).length > 0;
|
|
417
431
|
}
|
|
418
432
|
function assertDependencyPolicyAccepted(receipt) {
|
|
419
433
|
if (receipt.status !== 'reject')
|
|
@@ -495,8 +509,9 @@ async function uploadComponent(argv) {
|
|
|
495
509
|
'x-promptframe-upload-target': uploadTarget,
|
|
496
510
|
},
|
|
497
511
|
}, 'upload.http.failed');
|
|
498
|
-
const
|
|
499
|
-
|
|
512
|
+
const safePayload = sanitizePublicUploadPayload(payload);
|
|
513
|
+
const output = sanitizePublicUploadPayload({
|
|
514
|
+
...safePayload,
|
|
500
515
|
command: 'upload',
|
|
501
516
|
endpoint,
|
|
502
517
|
uploadTarget,
|
|
@@ -505,7 +520,7 @@ async function uploadComponent(argv) {
|
|
|
505
520
|
source: input.source,
|
|
506
521
|
...(localReusability ? { localReusability, diagnostics: localDiagnostics } : {}),
|
|
507
522
|
diagnostic: diagnostic('upload.completed', 'info', 'Component upload accepted by platform.'),
|
|
508
|
-
};
|
|
523
|
+
});
|
|
509
524
|
if (hasFlag(argv, '--json')) {
|
|
510
525
|
printJson(output);
|
|
511
526
|
return;
|
|
@@ -513,10 +528,56 @@ async function uploadComponent(argv) {
|
|
|
513
528
|
console.log('Upload accepted by PromptFrame platform.');
|
|
514
529
|
console.log(`Build: ${output.jobId ?? 'unknown'}`);
|
|
515
530
|
console.log(`Status: ${stringValue(payload.status) ?? stringValue(asRecord(payload.build)?.status) ?? 'queued'}`);
|
|
531
|
+
if (output.jobId) {
|
|
532
|
+
console.log(`Next: promptframe status ${output.jobId} --endpoint ${endpoint}`);
|
|
533
|
+
}
|
|
516
534
|
if (localDiagnostics)
|
|
517
535
|
printDiagnostics(localDiagnostics);
|
|
518
536
|
printStatusUrl(endpoint, payload);
|
|
519
537
|
}
|
|
538
|
+
const PUBLIC_UPLOAD_JSON_OMIT_KEYS = new Set([
|
|
539
|
+
'providerUsageReceipt',
|
|
540
|
+
'providerUsageReceipts',
|
|
541
|
+
'vectorRef',
|
|
542
|
+
'retryKey',
|
|
543
|
+
'sourceHash',
|
|
544
|
+
'sourceHashDetails',
|
|
545
|
+
'internalSourceHash',
|
|
546
|
+
'schemaHash',
|
|
547
|
+
'manifestHash',
|
|
548
|
+
'bundleHash',
|
|
549
|
+
'evidence',
|
|
550
|
+
'evidenceRecords',
|
|
551
|
+
'evidenceItems',
|
|
552
|
+
'rawEvidence',
|
|
553
|
+
]);
|
|
554
|
+
function sanitizePublicUploadPayload(value) {
|
|
555
|
+
return asRecord(sanitizePublicUploadValue(value)) ?? {};
|
|
556
|
+
}
|
|
557
|
+
function sanitizePublicUploadValue(value) {
|
|
558
|
+
if (typeof value === 'string') {
|
|
559
|
+
return scrubInternalReferences(value);
|
|
560
|
+
}
|
|
561
|
+
if (Array.isArray(value)) {
|
|
562
|
+
return value.map((item) => sanitizePublicUploadValue(item));
|
|
563
|
+
}
|
|
564
|
+
const record = asRecord(value);
|
|
565
|
+
if (!record)
|
|
566
|
+
return value;
|
|
567
|
+
const output = {};
|
|
568
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
569
|
+
if (PUBLIC_UPLOAD_JSON_OMIT_KEYS.has(key))
|
|
570
|
+
continue;
|
|
571
|
+
output[key] = sanitizePublicUploadValue(entry);
|
|
572
|
+
}
|
|
573
|
+
return output;
|
|
574
|
+
}
|
|
575
|
+
function scrubInternalReferences(value) {
|
|
576
|
+
return value
|
|
577
|
+
.replace(/\bREQ-\d+\b/g, 'internal reference')
|
|
578
|
+
.replace(/\bTASK-\d+(?:-[A-Za-z0-9-]+)?\b/g, 'internal task')
|
|
579
|
+
.replace(/\bBUG-\d+\b/g, 'internal bug');
|
|
580
|
+
}
|
|
520
581
|
function resolveUploadTarget(argv) {
|
|
521
582
|
const raw = normalizeUploadTargetAlias(valueAfter(argv, '--target') ?? valueAfter(argv, '--upload-target') ?? 'marketplace_authoring');
|
|
522
583
|
const parsed = authoringUploadTargetSchema.safeParse(raw);
|
|
@@ -874,7 +935,10 @@ const POSITIONAL_VALUE_FLAGS = new Set([
|
|
|
874
935
|
'--auth-permissions',
|
|
875
936
|
'--auth-roles',
|
|
876
937
|
'--component-path',
|
|
938
|
+
'--component-id',
|
|
877
939
|
'--config',
|
|
940
|
+
'--description',
|
|
941
|
+
'--display-name',
|
|
878
942
|
'--endpoint',
|
|
879
943
|
'--expires-at',
|
|
880
944
|
'--host',
|
|
@@ -887,6 +951,9 @@ const POSITIONAL_VALUE_FLAGS = new Set([
|
|
|
887
951
|
'--provider',
|
|
888
952
|
'--provider-kind',
|
|
889
953
|
'--provider-name',
|
|
954
|
+
'--project-id',
|
|
955
|
+
'--project-context',
|
|
956
|
+
'--project-namespace',
|
|
890
957
|
'--reason',
|
|
891
958
|
'--scope',
|
|
892
959
|
'--session-id',
|
|
@@ -896,6 +963,7 @@ const POSITIONAL_VALUE_FLAGS = new Set([
|
|
|
896
963
|
'--token',
|
|
897
964
|
'--upload-target',
|
|
898
965
|
'--user-id',
|
|
966
|
+
'--workspace-config',
|
|
899
967
|
'--workspace-component',
|
|
900
968
|
]);
|
|
901
969
|
function componentPathArg(argv) {
|
|
@@ -934,9 +1002,13 @@ async function showStatus(argv) {
|
|
|
934
1002
|
};
|
|
935
1003
|
if (hasFlag(argv, '--json')) {
|
|
936
1004
|
printJson(output);
|
|
937
|
-
return;
|
|
938
1005
|
}
|
|
939
|
-
|
|
1006
|
+
else {
|
|
1007
|
+
printBuildSummary(output);
|
|
1008
|
+
}
|
|
1009
|
+
if (hasFlag(argv, '--fail-on-build-failed')) {
|
|
1010
|
+
failIfBuildFailed(output);
|
|
1011
|
+
}
|
|
940
1012
|
}
|
|
941
1013
|
async function reindexEvidence(argv) {
|
|
942
1014
|
const buildId = requiredArg(argv[0], 'reindex requires a build id', 'reindex.build_id.missing');
|
|
@@ -1158,6 +1230,54 @@ async function discovery(argv) {
|
|
|
1158
1230
|
if (uploadTargets.length > 0)
|
|
1159
1231
|
console.log(`Upload targets: ${uploadTargets.join(', ')}`);
|
|
1160
1232
|
}
|
|
1233
|
+
async function initProjectContext(argv) {
|
|
1234
|
+
const root = resolve(componentPathArg(argv) ?? '.');
|
|
1235
|
+
const contextPath = join(root, PROMPTFRAME_PROJECT_CONTEXT_FILE_NAME);
|
|
1236
|
+
if (existsSync(contextPath) && !hasFlag(argv, '--force')) {
|
|
1237
|
+
fail(`PromptFrame project context already exists at ${contextPath}. Use --force to overwrite.`, 'project_context.exists', 2);
|
|
1238
|
+
}
|
|
1239
|
+
const endpoint = resolveEndpoint('init', argv);
|
|
1240
|
+
const payload = await fetchProjects(endpoint, argv);
|
|
1241
|
+
const projects = arrayValue(payload.projects).map((item) => asRecord(item)).filter((item) => Boolean(item));
|
|
1242
|
+
const requestedProjectId = valueAfter(argv, '--project-id');
|
|
1243
|
+
const currentProjectId = requestedProjectId ?? stringValue(payload.currentProjectId);
|
|
1244
|
+
const project = projects.find((item) => stringValue(item.projectId) === currentProjectId || (!requestedProjectId && item.isCurrent === true));
|
|
1245
|
+
if (!project) {
|
|
1246
|
+
fail(requestedProjectId
|
|
1247
|
+
? `PromptFrame project ${requestedProjectId} is not available for this credential.`
|
|
1248
|
+
: 'PromptFrame did not report an active project for this credential.', 'project_context.project_missing', 2);
|
|
1249
|
+
}
|
|
1250
|
+
const projectId = requiredArg(stringValue(project.projectId), 'PromptFrame project response did not include projectId.', 'project_context.project_id_missing');
|
|
1251
|
+
const tenantId = stringValue(project.tenantId);
|
|
1252
|
+
const context = buildProjectContext({
|
|
1253
|
+
endpoint,
|
|
1254
|
+
tenantId,
|
|
1255
|
+
projectId,
|
|
1256
|
+
projectNamespace: valueAfter(argv, '--project-namespace')
|
|
1257
|
+
?? stringValue(project.projectNamespace)
|
|
1258
|
+
?? normalizeProjectNamespace(projectId),
|
|
1259
|
+
defaultUploadTarget: valueAfter(argv, '--target')
|
|
1260
|
+
?? valueAfter(argv, '--upload-target')
|
|
1261
|
+
?? 'marketplace_authoring',
|
|
1262
|
+
workspaceConfig: valueAfter(argv, '--workspace-config') ?? 'promptframe-workspace.json',
|
|
1263
|
+
});
|
|
1264
|
+
mkdirSync(root, { recursive: true });
|
|
1265
|
+
writeFileSync(contextPath, `${JSON.stringify(context, null, 2)}\n`, 'utf8');
|
|
1266
|
+
const output = {
|
|
1267
|
+
command: 'init',
|
|
1268
|
+
endpoint,
|
|
1269
|
+
contextPath,
|
|
1270
|
+
context,
|
|
1271
|
+
project,
|
|
1272
|
+
diagnostic: diagnostic('project_context.init.completed', 'info', 'PromptFrame project context written.'),
|
|
1273
|
+
};
|
|
1274
|
+
if (hasFlag(argv, '--json')) {
|
|
1275
|
+
printJson(output);
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
console.log(`Wrote PromptFrame project context: ${contextPath}`);
|
|
1279
|
+
console.log(`Project: ${projectId}`);
|
|
1280
|
+
}
|
|
1161
1281
|
async function project(argv) {
|
|
1162
1282
|
const subcommand = argv[0] && !argv[0].startsWith('--') ? argv[0] : 'list';
|
|
1163
1283
|
if (subcommand !== 'list' && subcommand !== 'current') {
|
|
@@ -1214,6 +1334,84 @@ async function fetchProjects(endpoint, argv) {
|
|
|
1214
1334
|
headers: buildRemoteHeaders(endpoint, argv),
|
|
1215
1335
|
}, 'project.http.failed');
|
|
1216
1336
|
}
|
|
1337
|
+
async function component(argv) {
|
|
1338
|
+
const subcommand = argv[0] && !argv[0].startsWith('--') ? argv[0] : 'list';
|
|
1339
|
+
const rest = argv.slice(argv[0] && !argv[0].startsWith('--') ? 1 : 0);
|
|
1340
|
+
switch (subcommand) {
|
|
1341
|
+
case 'list':
|
|
1342
|
+
await componentList(rest);
|
|
1343
|
+
break;
|
|
1344
|
+
case 'create':
|
|
1345
|
+
await componentCreate(rest);
|
|
1346
|
+
break;
|
|
1347
|
+
default:
|
|
1348
|
+
fail(`Unknown component command: ${subcommand}`, 'component.command.unknown', 2);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
async function componentList(argv) {
|
|
1352
|
+
const endpoint = resolveEndpoint('component', argv);
|
|
1353
|
+
const params = new URLSearchParams();
|
|
1354
|
+
const componentId = valueAfter(argv, '--component-id') ?? positionalArgs(argv)[0];
|
|
1355
|
+
if (componentId)
|
|
1356
|
+
params.set('componentId', assertComponentId(componentId));
|
|
1357
|
+
const query = params.toString();
|
|
1358
|
+
const payload = await fetchJson(`${endpoint}/components/marketplace/self/components${query ? `?${query}` : ''}`, {
|
|
1359
|
+
headers: buildRemoteHeaders(endpoint, argv),
|
|
1360
|
+
}, 'component.list.http.failed');
|
|
1361
|
+
const output = {
|
|
1362
|
+
...payload,
|
|
1363
|
+
command: 'component.list',
|
|
1364
|
+
endpoint,
|
|
1365
|
+
diagnostic: diagnostic('component.list.completed', 'info', 'PromptFrame project components listed.'),
|
|
1366
|
+
};
|
|
1367
|
+
if (hasFlag(argv, '--json')) {
|
|
1368
|
+
printJson(output);
|
|
1369
|
+
return;
|
|
1370
|
+
}
|
|
1371
|
+
for (const item of arrayValue(payload.declarations)) {
|
|
1372
|
+
const declaration = asRecord(item);
|
|
1373
|
+
if (declaration)
|
|
1374
|
+
printComponentDeclarationLine(declaration);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
async function componentCreate(argv) {
|
|
1378
|
+
const endpoint = resolveEndpoint('component', argv);
|
|
1379
|
+
const componentId = assertComponentId(requiredArg(positionalArgs(argv)[0], 'component create requires <component-id>.', 'component.id_missing'));
|
|
1380
|
+
const displayName = requiredArg(valueAfter(argv, '--display-name') ?? valueAfter(argv, '--name') ?? displayNameFromComponentId(componentId), 'component create requires --display-name <name>.', 'component.display_name_missing');
|
|
1381
|
+
const description = valueAfter(argv, '--description');
|
|
1382
|
+
const payload = compactRecord({
|
|
1383
|
+
componentId,
|
|
1384
|
+
displayName,
|
|
1385
|
+
description,
|
|
1386
|
+
});
|
|
1387
|
+
const response = await fetchJson(`${endpoint}/components/marketplace/self/components`, {
|
|
1388
|
+
method: 'POST',
|
|
1389
|
+
headers: {
|
|
1390
|
+
...buildRemoteHeaders(endpoint, argv),
|
|
1391
|
+
'content-type': 'application/json',
|
|
1392
|
+
},
|
|
1393
|
+
body: JSON.stringify(payload),
|
|
1394
|
+
}, 'component.create.http.failed');
|
|
1395
|
+
const output = {
|
|
1396
|
+
...response,
|
|
1397
|
+
command: 'component.create',
|
|
1398
|
+
endpoint,
|
|
1399
|
+
diagnostic: diagnostic('component.create.completed', 'info', 'PromptFrame project component declared.'),
|
|
1400
|
+
};
|
|
1401
|
+
if (hasFlag(argv, '--json')) {
|
|
1402
|
+
printJson(output);
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
const declaration = asRecord(response.declaration);
|
|
1406
|
+
if (declaration)
|
|
1407
|
+
printComponentDeclarationLine(declaration);
|
|
1408
|
+
}
|
|
1409
|
+
function printComponentDeclarationLine(declaration) {
|
|
1410
|
+
const componentId = stringValue(declaration.componentId) ?? 'unknown';
|
|
1411
|
+
const status = stringValue(declaration.status) ?? 'unknown';
|
|
1412
|
+
const displayName = stringValue(declaration.displayName) ?? componentId;
|
|
1413
|
+
console.log(`${componentId}\t${status}\t${displayName}`);
|
|
1414
|
+
}
|
|
1217
1415
|
function printProjectLine(project, current) {
|
|
1218
1416
|
const prefix = current ? '*' : '-';
|
|
1219
1417
|
const projectId = stringValue(project.projectId) ?? 'unknown';
|
|
@@ -1428,6 +1626,7 @@ function setupCi(argv) {
|
|
|
1428
1626
|
workflowPath,
|
|
1429
1627
|
requiredSecrets: ['PROMPTFRAME_CI_TOKEN'],
|
|
1430
1628
|
requiredVariables: ['PROMPTFRAME_API_BASE'],
|
|
1629
|
+
...(workspaceMode ? { optionalVariables: ['RUNNER_LABELS'] } : {}),
|
|
1431
1630
|
pullRequestMode: 'check_only',
|
|
1432
1631
|
pushMode: 'upload',
|
|
1433
1632
|
diagnostic: diagnostic('setup_ci.github.completed', 'info', 'PromptFrame GitHub Actions workflow written.'),
|
|
@@ -1451,18 +1650,20 @@ function setupCiTargetDir(argv) {
|
|
|
1451
1650
|
}
|
|
1452
1651
|
return '.';
|
|
1453
1652
|
}
|
|
1454
|
-
function promptFrameGithubWorkspaceWorkflow(
|
|
1653
|
+
function promptFrameGithubWorkspaceWorkflow(_components) {
|
|
1455
1654
|
const apiBaseVariable = '$' + '{{ vars.PROMPTFRAME_API_BASE }}';
|
|
1456
1655
|
const ciTokenSecret = '$' + '{{ secrets.PROMPTFRAME_CI_TOKEN }}';
|
|
1457
1656
|
const matrixComponentId = '$' + '{{ matrix.component.componentId }}';
|
|
1458
1657
|
const matrixComponentPath = '$' + '{{ matrix.component.componentPath }}';
|
|
1459
1658
|
const matrixArtifactName = '$' + '{{ matrix.component.artifactName }}';
|
|
1460
|
-
const
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1659
|
+
const runnerLabels = '$' + '{{ vars.RUNNER_LABELS && fromJSON(vars.RUNNER_LABELS) || fromJSON(\'["ubuntu-latest"]\') }}';
|
|
1660
|
+
const workspaceMatrixOutput = '$' + '{{ steps.workspace.outputs.matrix }}';
|
|
1661
|
+
const workspaceCountOutput = '$' + '{{ steps.workspace.outputs.count }}';
|
|
1662
|
+
const discoverMatrix = '$' + '{{ fromJSON(needs.discover.outputs.matrix) }}';
|
|
1663
|
+
return `# promptframe-workflow-version: 1
|
|
1664
|
+
# Generated by promptframe setup-ci --workspace. Keep this header when editing manually.
|
|
1665
|
+
|
|
1666
|
+
name: PromptFrame Component Workspace
|
|
1466
1667
|
|
|
1467
1668
|
on:
|
|
1468
1669
|
pull_request:
|
|
@@ -1477,36 +1678,98 @@ permissions:
|
|
|
1477
1678
|
contents: read
|
|
1478
1679
|
|
|
1479
1680
|
jobs:
|
|
1681
|
+
discover:
|
|
1682
|
+
name: Discover PromptFrame components
|
|
1683
|
+
runs-on: ${runnerLabels}
|
|
1684
|
+
outputs:
|
|
1685
|
+
matrix: ${workspaceMatrixOutput}
|
|
1686
|
+
count: ${workspaceCountOutput}
|
|
1687
|
+
steps:
|
|
1688
|
+
- uses: actions/checkout@v4
|
|
1689
|
+
- uses: actions/setup-node@v4
|
|
1690
|
+
with:
|
|
1691
|
+
node-version: '20'
|
|
1692
|
+
- id: workspace
|
|
1693
|
+
name: Read promptframe-workspace.json
|
|
1694
|
+
run: |
|
|
1695
|
+
node <<'NODE'
|
|
1696
|
+
const fs = require('node:fs');
|
|
1697
|
+
const path = require('node:path');
|
|
1698
|
+
const configPath = 'promptframe-workspace.json';
|
|
1699
|
+
function fail(message) {
|
|
1700
|
+
console.error(message);
|
|
1701
|
+
process.exit(1);
|
|
1702
|
+
}
|
|
1703
|
+
function artifactName(value) {
|
|
1704
|
+
return String(value || 'component')
|
|
1705
|
+
.replace(/^@/, '')
|
|
1706
|
+
.replace(/[^A-Za-z0-9_.-]+/g, '-')
|
|
1707
|
+
.replace(/^-+|-+$/g, '') || 'component';
|
|
1708
|
+
}
|
|
1709
|
+
if (!fs.existsSync(configPath)) {
|
|
1710
|
+
fail('promptframe-workspace.json is required for promptframe workspace CI.');
|
|
1711
|
+
}
|
|
1712
|
+
const workspace = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
1713
|
+
const components = Array.isArray(workspace.components) ? workspace.components : [];
|
|
1714
|
+
const include = components.map((component, index) => {
|
|
1715
|
+
if (!component || typeof component.id !== 'string' || typeof component.path !== 'string') {
|
|
1716
|
+
fail('promptframe-workspace.json components[' + index + '] must include string id and path.');
|
|
1717
|
+
}
|
|
1718
|
+
const manifestPath = path.join(component.path, 'manifest.json');
|
|
1719
|
+
if (!fs.existsSync(manifestPath)) {
|
|
1720
|
+
fail('Workspace component manifest not found: ' + manifestPath);
|
|
1721
|
+
}
|
|
1722
|
+
return {
|
|
1723
|
+
componentId: component.id,
|
|
1724
|
+
componentPath: component.path,
|
|
1725
|
+
artifactName: artifactName(component.id),
|
|
1726
|
+
};
|
|
1727
|
+
});
|
|
1728
|
+
const output = process.env.GITHUB_OUTPUT;
|
|
1729
|
+
if (!output) fail('GITHUB_OUTPUT is not available.');
|
|
1730
|
+
fs.appendFileSync(output, 'matrix=' + JSON.stringify({ include }) + '\\n');
|
|
1731
|
+
fs.appendFileSync(output, 'count=' + String(include.length) + '\\n');
|
|
1732
|
+
const summary = process.env.GITHUB_STEP_SUMMARY;
|
|
1733
|
+
if (summary) {
|
|
1734
|
+
const rows = include.map((item) => '- \`' + item.componentId + '\` -> \`' + item.componentPath + '\`');
|
|
1735
|
+
fs.appendFileSync(summary, ['### PromptFrame workspace discover', '', '- Components: \`' + include.length + '\`', ...rows, ''].join('\\n'));
|
|
1736
|
+
}
|
|
1737
|
+
NODE
|
|
1738
|
+
|
|
1480
1739
|
check:
|
|
1481
1740
|
name: PromptFrame check (${matrixComponentId})
|
|
1482
|
-
|
|
1741
|
+
needs: discover
|
|
1742
|
+
if: needs.discover.outputs.count != '0'
|
|
1743
|
+
runs-on: ${runnerLabels}
|
|
1483
1744
|
strategy:
|
|
1484
1745
|
fail-fast: false
|
|
1485
|
-
matrix:
|
|
1486
|
-
component:
|
|
1487
|
-
${matrixItems}
|
|
1746
|
+
matrix: ${discoverMatrix}
|
|
1488
1747
|
steps:
|
|
1489
1748
|
- uses: actions/checkout@v4
|
|
1490
1749
|
- uses: actions/setup-node@v4
|
|
1491
1750
|
with:
|
|
1492
1751
|
node-version: '20'
|
|
1493
|
-
cache:
|
|
1494
|
-
- run: npm install
|
|
1752
|
+
cache: pnpm
|
|
1753
|
+
- run: npm install -g pnpm@10
|
|
1754
|
+
- run: pnpm install --no-frozen-lockfile
|
|
1495
1755
|
- name: Validate workspace
|
|
1496
|
-
|
|
1756
|
+
env:
|
|
1757
|
+
ARTIFACT_NAME: ${matrixArtifactName}
|
|
1758
|
+
run: npx promptframe workspace validate . --json | tee "promptframe-workspace-report-$ARTIFACT_NAME.json"
|
|
1497
1759
|
- name: Validate component package
|
|
1498
1760
|
env:
|
|
1499
1761
|
PROMPTFRAME_API_BASE: ${apiBaseVariable}
|
|
1500
1762
|
COMPONENT_ID: ${matrixComponentId}
|
|
1501
1763
|
COMPONENT_PATH: ${matrixComponentPath}
|
|
1764
|
+
ARTIFACT_NAME: ${matrixArtifactName}
|
|
1502
1765
|
run: |
|
|
1503
1766
|
set +e
|
|
1504
|
-
npx promptframe check . --workspace-component "$COMPONENT_ID" --json > promptframe-check.json
|
|
1767
|
+
npx promptframe check . --workspace-component "$COMPONENT_ID" --json > "promptframe-check-$ARTIFACT_NAME.json"
|
|
1505
1768
|
CHECK_EXIT=$?
|
|
1506
1769
|
set -e
|
|
1507
1770
|
node <<'NODE'
|
|
1508
1771
|
const fs = require('node:fs');
|
|
1509
|
-
const payload = JSON.parse(fs.readFileSync('promptframe-check.json', 'utf8'));
|
|
1772
|
+
const payload = JSON.parse(fs.readFileSync('promptframe-check-' + process.env.ARTIFACT_NAME + '.json', 'utf8'));
|
|
1510
1773
|
const diagnostics = Array.isArray(payload.diagnostics) ? payload.diagnostics : [];
|
|
1511
1774
|
for (const item of diagnostics) {
|
|
1512
1775
|
const code = String(item.code || 'promptframe.diagnostic');
|
|
@@ -1535,40 +1798,40 @@ ${matrixItems}
|
|
|
1535
1798
|
- uses: actions/upload-artifact@v4
|
|
1536
1799
|
if: always()
|
|
1537
1800
|
with:
|
|
1538
|
-
name: promptframe-check
|
|
1801
|
+
name: promptframe-check-${matrixArtifactName}
|
|
1539
1802
|
path: |
|
|
1540
|
-
promptframe-workspace-report.json
|
|
1541
|
-
promptframe-check.json
|
|
1803
|
+
promptframe-workspace-report-${matrixArtifactName}.json
|
|
1804
|
+
promptframe-check-${matrixArtifactName}.json
|
|
1542
1805
|
|
|
1543
1806
|
upload:
|
|
1544
1807
|
name: PromptFrame upload (${matrixComponentId})
|
|
1545
|
-
if: github.event_name == 'push'
|
|
1546
|
-
needs: check
|
|
1547
|
-
runs-on:
|
|
1808
|
+
if: github.event_name == 'push' && needs.discover.outputs.count != '0'
|
|
1809
|
+
needs: [discover, check]
|
|
1810
|
+
runs-on: ${runnerLabels}
|
|
1548
1811
|
strategy:
|
|
1549
1812
|
fail-fast: false
|
|
1550
|
-
matrix:
|
|
1551
|
-
component:
|
|
1552
|
-
${matrixItems}
|
|
1813
|
+
matrix: ${discoverMatrix}
|
|
1553
1814
|
steps:
|
|
1554
1815
|
- uses: actions/checkout@v4
|
|
1555
1816
|
- uses: actions/setup-node@v4
|
|
1556
1817
|
with:
|
|
1557
1818
|
node-version: '20'
|
|
1558
|
-
cache:
|
|
1559
|
-
- run: npm install
|
|
1819
|
+
cache: pnpm
|
|
1820
|
+
- run: npm install -g pnpm@10
|
|
1821
|
+
- run: pnpm install --no-frozen-lockfile
|
|
1560
1822
|
- name: Upload component to PromptFrame
|
|
1561
1823
|
env:
|
|
1562
1824
|
PROMPTFRAME_API_BASE: ${apiBaseVariable}
|
|
1563
1825
|
PROMPTFRAME_CI_TOKEN: ${ciTokenSecret}
|
|
1564
1826
|
COMPONENT_ID: ${matrixComponentId}
|
|
1565
1827
|
COMPONENT_PATH: ${matrixComponentPath}
|
|
1828
|
+
ARTIFACT_NAME: ${matrixArtifactName}
|
|
1566
1829
|
run: |
|
|
1567
1830
|
set -euo pipefail
|
|
1568
|
-
npx promptframe upload . --workspace-component "$COMPONENT_ID" --endpoint "$PROMPTFRAME_API_BASE" --json | tee promptframe-upload.json
|
|
1831
|
+
npx promptframe upload . --workspace-component "$COMPONENT_ID" --endpoint "$PROMPTFRAME_API_BASE" --json | tee "promptframe-upload-$ARTIFACT_NAME.json"
|
|
1569
1832
|
BUILD_ID=$(node <<'NODE'
|
|
1570
1833
|
const fs = require('node:fs');
|
|
1571
|
-
const payload = JSON.parse(fs.readFileSync('promptframe-upload.json', 'utf8'));
|
|
1834
|
+
const payload = JSON.parse(fs.readFileSync('promptframe-upload-' + process.env.ARTIFACT_NAME + '.json', 'utf8'));
|
|
1572
1835
|
process.stdout.write(String(payload.jobId || payload.buildId || payload.build?.buildId || ''));
|
|
1573
1836
|
NODE
|
|
1574
1837
|
)
|
|
@@ -1576,11 +1839,28 @@ ${matrixItems}
|
|
|
1576
1839
|
echo "PromptFrame upload response did not include a build id." >&2
|
|
1577
1840
|
exit 1
|
|
1578
1841
|
fi
|
|
1579
|
-
|
|
1842
|
+
set +e
|
|
1843
|
+
npx promptframe status "$BUILD_ID" --endpoint "$PROMPTFRAME_API_BASE" --json --fail-on-build-failed | tee "promptframe-status-$ARTIFACT_NAME.json"
|
|
1844
|
+
STATUS_EXIT=\${PIPESTATUS[0]}
|
|
1845
|
+
set -e
|
|
1580
1846
|
node <<'NODE'
|
|
1581
1847
|
const fs = require('node:fs');
|
|
1582
|
-
const payload = JSON.parse(fs.readFileSync('promptframe-upload.json', 'utf8'));
|
|
1583
|
-
const status = JSON.parse(fs.readFileSync('promptframe-status.json', 'utf8'));
|
|
1848
|
+
const payload = JSON.parse(fs.readFileSync('promptframe-upload-' + process.env.ARTIFACT_NAME + '.json', 'utf8'));
|
|
1849
|
+
const status = JSON.parse(fs.readFileSync('promptframe-status-' + process.env.ARTIFACT_NAME + '.json', 'utf8'));
|
|
1850
|
+
const build = status.build || {};
|
|
1851
|
+
const buildId = payload.jobId || payload.buildId || build.buildId || 'unknown';
|
|
1852
|
+
const buildStatus = String(build.status || status.status || payload.status || 'queued');
|
|
1853
|
+
const diagnostics = Array.isArray(build.diagnostics) ? build.diagnostics : [];
|
|
1854
|
+
const firstError = diagnostics.find((item) => item && item.severity === 'error') || diagnostics[0] || {};
|
|
1855
|
+
if (['failed', 'cancelled', 'canceled'].includes(buildStatus.toLowerCase())) {
|
|
1856
|
+
const message = [
|
|
1857
|
+
\`buildId=\${buildId}\`,
|
|
1858
|
+
\`status=\${buildStatus}\`,
|
|
1859
|
+
firstError.code ? \`code=\${firstError.code}\` : '',
|
|
1860
|
+
firstError.message ? \`message=\${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
1861
|
+
].filter(Boolean).join(' ');
|
|
1862
|
+
console.log(\`::error title=PromptFrame platform build failed::\${message}\`);
|
|
1863
|
+
}
|
|
1584
1864
|
const summary = process.env.GITHUB_STEP_SUMMARY;
|
|
1585
1865
|
if (summary) {
|
|
1586
1866
|
fs.appendFileSync(summary, [
|
|
@@ -1589,28 +1869,24 @@ ${matrixItems}
|
|
|
1589
1869
|
\`- Component ID: \\\`\${process.env.COMPONENT_ID || payload.source?.workspaceComponentId || 'unknown'}\\\`\`,
|
|
1590
1870
|
\`- Component path: \\\`\${process.env.COMPONENT_PATH || payload.source?.componentPath || 'unknown'}\\\`\`,
|
|
1591
1871
|
\`- Diagnostic: \\\`\${payload.diagnostic?.code || 'unknown'}\\\`\`,
|
|
1592
|
-
\`- Build ID: \\\`\${
|
|
1593
|
-
\`-
|
|
1872
|
+
\`- Build ID: \\\`\${buildId}\\\`\`,
|
|
1873
|
+
\`- Platform build status: \\\`\${buildStatus}\\\`\`,
|
|
1874
|
+
firstError.code ? \`- Top diagnostic: \\\`\${firstError.code}\\\`\` : '- Top diagnostic: none',
|
|
1875
|
+
firstError.message ? \`- Message: \${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
1594
1876
|
'',
|
|
1595
|
-
].join('\\n'));
|
|
1877
|
+
].filter(Boolean).join('\\n'));
|
|
1596
1878
|
}
|
|
1597
1879
|
NODE
|
|
1880
|
+
exit "$STATUS_EXIT"
|
|
1598
1881
|
- uses: actions/upload-artifact@v4
|
|
1599
1882
|
if: always()
|
|
1600
1883
|
with:
|
|
1601
|
-
name: promptframe-upload
|
|
1884
|
+
name: promptframe-upload-${matrixArtifactName}
|
|
1602
1885
|
path: |
|
|
1603
|
-
promptframe-upload.json
|
|
1604
|
-
promptframe-status.json
|
|
1886
|
+
promptframe-upload-${matrixArtifactName}.json
|
|
1887
|
+
promptframe-status-${matrixArtifactName}.json
|
|
1605
1888
|
`;
|
|
1606
1889
|
}
|
|
1607
|
-
function githubMatrixArtifactName(componentId) {
|
|
1608
|
-
return componentId
|
|
1609
|
-
.replace(/^@/, '')
|
|
1610
|
-
.replace(/[^A-Za-z0-9_.-]+/g, '-')
|
|
1611
|
-
.replace(/^-+|-+$/g, '')
|
|
1612
|
-
|| 'component';
|
|
1613
|
-
}
|
|
1614
1890
|
function promptFrameGithubWorkflow() {
|
|
1615
1891
|
const apiBaseVariable = '$' + '{{ vars.PROMPTFRAME_API_BASE }}';
|
|
1616
1892
|
const ciTokenSecret = '$' + '{{ secrets.PROMPTFRAME_CI_TOKEN }}';
|
|
@@ -1707,23 +1983,43 @@ jobs:
|
|
|
1707
1983
|
echo "PromptFrame upload response did not include a build id." >&2
|
|
1708
1984
|
exit 1
|
|
1709
1985
|
fi
|
|
1710
|
-
|
|
1986
|
+
set +e
|
|
1987
|
+
npx promptframe status "$BUILD_ID" --endpoint "$PROMPTFRAME_API_BASE" --json --fail-on-build-failed | tee promptframe-status.json
|
|
1988
|
+
STATUS_EXIT=\${PIPESTATUS[0]}
|
|
1989
|
+
set -e
|
|
1711
1990
|
node <<'NODE'
|
|
1712
1991
|
const fs = require('node:fs');
|
|
1713
1992
|
const payload = JSON.parse(fs.readFileSync('promptframe-upload.json', 'utf8'));
|
|
1714
1993
|
const status = JSON.parse(fs.readFileSync('promptframe-status.json', 'utf8'));
|
|
1994
|
+
const build = status.build || {};
|
|
1995
|
+
const buildId = payload.jobId || payload.buildId || build.buildId || 'unknown';
|
|
1996
|
+
const buildStatus = String(build.status || status.status || payload.status || 'queued');
|
|
1997
|
+
const diagnostics = Array.isArray(build.diagnostics) ? build.diagnostics : [];
|
|
1998
|
+
const firstError = diagnostics.find((item) => item && item.severity === 'error') || diagnostics[0] || {};
|
|
1999
|
+
if (['failed', 'cancelled', 'canceled'].includes(buildStatus.toLowerCase())) {
|
|
2000
|
+
const message = [
|
|
2001
|
+
\`buildId=\${buildId}\`,
|
|
2002
|
+
\`status=\${buildStatus}\`,
|
|
2003
|
+
firstError.code ? \`code=\${firstError.code}\` : '',
|
|
2004
|
+
firstError.message ? \`message=\${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
2005
|
+
].filter(Boolean).join(' ');
|
|
2006
|
+
console.log(\`::error title=PromptFrame platform build failed::\${message}\`);
|
|
2007
|
+
}
|
|
1715
2008
|
const summary = process.env.GITHUB_STEP_SUMMARY;
|
|
1716
2009
|
if (summary) {
|
|
1717
2010
|
fs.appendFileSync(summary, [
|
|
1718
2011
|
'### PromptFrame upload',
|
|
1719
2012
|
'',
|
|
1720
2013
|
\`- Diagnostic: \\\`\${payload.diagnostic?.code || 'unknown'}\\\`\`,
|
|
1721
|
-
\`- Build ID: \\\`\${
|
|
1722
|
-
\`-
|
|
2014
|
+
\`- Build ID: \\\`\${buildId}\\\`\`,
|
|
2015
|
+
\`- Platform build status: \\\`\${buildStatus}\\\`\`,
|
|
2016
|
+
firstError.code ? \`- Top diagnostic: \\\`\${firstError.code}\\\`\` : '- Top diagnostic: none',
|
|
2017
|
+
firstError.message ? \`- Message: \${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
1723
2018
|
'',
|
|
1724
|
-
].join('\\n'));
|
|
2019
|
+
].filter(Boolean).join('\\n'));
|
|
1725
2020
|
}
|
|
1726
2021
|
NODE
|
|
2022
|
+
exit "$STATUS_EXIT"
|
|
1727
2023
|
- uses: actions/upload-artifact@v4
|
|
1728
2024
|
if: always()
|
|
1729
2025
|
with:
|
|
@@ -1735,21 +2031,29 @@ jobs:
|
|
|
1735
2031
|
}
|
|
1736
2032
|
function resolveEndpoint(commandName, argv) {
|
|
1737
2033
|
const config = readConfig(argv);
|
|
2034
|
+
const credential = asRecord(config.credential);
|
|
2035
|
+
const projectContext = readProjectContext(argv);
|
|
1738
2036
|
const endpoint = valueAfter(argv, '--endpoint')
|
|
1739
2037
|
?? process.env.PROMPTFRAME_API_BASE
|
|
1740
2038
|
?? process.env.REMOTION_MEDIA_API_BASE
|
|
1741
|
-
?? stringValue(config.endpoint)
|
|
2039
|
+
?? stringValue(config.endpoint)
|
|
2040
|
+
?? stringValue(credential?.endpoint)
|
|
2041
|
+
?? projectContext?.endpoint;
|
|
1742
2042
|
if (!endpoint) {
|
|
1743
|
-
fail(`${commandName} requires --endpoint, PROMPTFRAME_API_BASE, REMOTION_MEDIA_API_BASE,
|
|
2043
|
+
fail(`${commandName} requires --endpoint, PROMPTFRAME_API_BASE, REMOTION_MEDIA_API_BASE, local config, or .promptframerc. No default production endpoint is embedded in the public CLI.`, `${commandName}.endpoint.missing`, 2);
|
|
1744
2044
|
}
|
|
1745
2045
|
return normalizeEndpoint(endpoint);
|
|
1746
2046
|
}
|
|
1747
2047
|
function resolveOptionalEndpoint(argv) {
|
|
1748
2048
|
const config = readConfig(argv);
|
|
2049
|
+
const credential = asRecord(config.credential);
|
|
2050
|
+
const projectContext = readProjectContext(argv);
|
|
1749
2051
|
const endpoint = valueAfter(argv, '--endpoint')
|
|
1750
2052
|
?? stringValue(process.env.PROMPTFRAME_API_BASE)
|
|
1751
2053
|
?? stringValue(process.env.REMOTION_MEDIA_API_BASE)
|
|
1752
|
-
?? stringValue(config.endpoint)
|
|
2054
|
+
?? stringValue(config.endpoint)
|
|
2055
|
+
?? stringValue(credential?.endpoint)
|
|
2056
|
+
?? projectContext?.endpoint;
|
|
1753
2057
|
return endpoint ? normalizeEndpoint(endpoint) : undefined;
|
|
1754
2058
|
}
|
|
1755
2059
|
function buildContextHeaders(argv) {
|
|
@@ -1778,7 +2082,7 @@ function buildRemoteHeaders(endpoint, argv, options = {}) {
|
|
|
1778
2082
|
'x-auth-permissions',
|
|
1779
2083
|
].includes(name));
|
|
1780
2084
|
if (isFormalEndpoint(endpoint) && hasDevHeaders) {
|
|
1781
|
-
fail(
|
|
2085
|
+
fail(`Formal PromptFrame endpoints do not accept dev identity headers. Run promptframe login --endpoint ${endpoint}, or set PROMPTFRAME_CI_TOKEN to a scoped CI token and retry without --auth-roles/--auth-permissions.`, 'cli.auth.dev_header_formal_endpoint_forbidden');
|
|
1782
2086
|
}
|
|
1783
2087
|
if (isFormalEndpoint(endpoint) && options.requireAuth !== false) {
|
|
1784
2088
|
fail('No PromptFrame CLI credential found for this endpoint. Run promptframe login --endpoint <url> or provide PROMPTFRAME_CI_TOKEN.', 'cli.auth.login_required', 2);
|
|
@@ -1918,6 +2222,110 @@ function writeConfig(argv, config) {
|
|
|
1918
2222
|
function resolveConfigPath(argv) {
|
|
1919
2223
|
return resolve(valueAfter(argv, '--config') ?? process.env.PROMPTFRAME_CONFIG ?? join(os.homedir(), '.promptframe', 'component-authoring.json'));
|
|
1920
2224
|
}
|
|
2225
|
+
function readProjectContext(argv) {
|
|
2226
|
+
const explicitPath = valueAfter(argv, '--project-context');
|
|
2227
|
+
const contextPath = explicitPath
|
|
2228
|
+
? resolve(explicitPath)
|
|
2229
|
+
: findProjectContextPath(process.cwd());
|
|
2230
|
+
if (!contextPath || !existsSync(contextPath))
|
|
2231
|
+
return undefined;
|
|
2232
|
+
let raw;
|
|
2233
|
+
try {
|
|
2234
|
+
raw = JSON.parse(readFileSync(contextPath, 'utf8'));
|
|
2235
|
+
}
|
|
2236
|
+
catch {
|
|
2237
|
+
fail(`PromptFrame project context is not valid JSON: ${contextPath}`, 'project_context.invalid_json', 2);
|
|
2238
|
+
}
|
|
2239
|
+
return parseProjectContext(raw, contextPath);
|
|
2240
|
+
}
|
|
2241
|
+
function findProjectContextPath(startDir) {
|
|
2242
|
+
let current = resolve(startDir);
|
|
2243
|
+
for (;;) {
|
|
2244
|
+
const candidate = join(current, PROMPTFRAME_PROJECT_CONTEXT_FILE_NAME);
|
|
2245
|
+
if (existsSync(candidate))
|
|
2246
|
+
return candidate;
|
|
2247
|
+
const parent = dirname(current);
|
|
2248
|
+
if (parent === current)
|
|
2249
|
+
return undefined;
|
|
2250
|
+
current = parent;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
function parseProjectContext(value, sourcePath) {
|
|
2254
|
+
const record = asRecord(value);
|
|
2255
|
+
if (!record) {
|
|
2256
|
+
fail(`PromptFrame project context must be a JSON object: ${sourcePath}`, 'project_context.invalid', 2);
|
|
2257
|
+
}
|
|
2258
|
+
assertProjectContextSecretFree(record, sourcePath);
|
|
2259
|
+
const schemaVersion = stringValue(record.schemaVersion);
|
|
2260
|
+
if (schemaVersion && schemaVersion !== PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION) {
|
|
2261
|
+
fail(`Unsupported PromptFrame project context schemaVersion ${schemaVersion}. Expected ${PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION}.`, 'project_context.schema_unsupported', 2);
|
|
2262
|
+
}
|
|
2263
|
+
return {
|
|
2264
|
+
schemaVersion: PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION,
|
|
2265
|
+
...(stringValue(record.endpoint) ? { endpoint: normalizeEndpoint(stringValue(record.endpoint)) } : {}),
|
|
2266
|
+
...(stringValue(record.tenantId) ? { tenantId: stringValue(record.tenantId) } : {}),
|
|
2267
|
+
...(stringValue(record.projectId) ? { projectId: stringValue(record.projectId) } : {}),
|
|
2268
|
+
...(stringValue(record.projectNamespace) ? { projectNamespace: stringValue(record.projectNamespace) } : {}),
|
|
2269
|
+
...(stringValue(record.defaultUploadTarget) ? { defaultUploadTarget: stringValue(record.defaultUploadTarget) } : {}),
|
|
2270
|
+
...(stringValue(record.workspaceConfig) ? { workspaceConfig: stringValue(record.workspaceConfig) } : {}),
|
|
2271
|
+
};
|
|
2272
|
+
}
|
|
2273
|
+
function buildProjectContext(input) {
|
|
2274
|
+
const context = {
|
|
2275
|
+
schemaVersion: PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION,
|
|
2276
|
+
endpoint: normalizeEndpoint(input.endpoint),
|
|
2277
|
+
...(input.tenantId ? { tenantId: input.tenantId } : {}),
|
|
2278
|
+
projectId: input.projectId,
|
|
2279
|
+
...(input.projectNamespace ? { projectNamespace: normalizeProjectNamespace(input.projectNamespace) } : {}),
|
|
2280
|
+
...(input.defaultUploadTarget ? { defaultUploadTarget: input.defaultUploadTarget } : {}),
|
|
2281
|
+
...(input.workspaceConfig ? { workspaceConfig: input.workspaceConfig } : {}),
|
|
2282
|
+
};
|
|
2283
|
+
assertProjectContextSecretFree(context, PROMPTFRAME_PROJECT_CONTEXT_FILE_NAME);
|
|
2284
|
+
return context;
|
|
2285
|
+
}
|
|
2286
|
+
function assertProjectContextSecretFree(value, sourcePath) {
|
|
2287
|
+
if (!value || typeof value !== 'object')
|
|
2288
|
+
return;
|
|
2289
|
+
if (Array.isArray(value)) {
|
|
2290
|
+
for (const item of value)
|
|
2291
|
+
assertProjectContextSecretFree(item, sourcePath);
|
|
2292
|
+
return;
|
|
2293
|
+
}
|
|
2294
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2295
|
+
const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
2296
|
+
if (PROJECT_CONTEXT_FORBIDDEN_KEY_NAMES.has(normalizedKey)) {
|
|
2297
|
+
fail(`PromptFrame project context must not contain secret-bearing field "${key}" in ${sourcePath}. Store tokens in the local CLI config or CI secret store instead.`, 'project_context.secret_field_forbidden', 2);
|
|
2298
|
+
}
|
|
2299
|
+
assertProjectContextSecretFree(child, sourcePath);
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function assertComponentId(value) {
|
|
2303
|
+
if (!/^@[a-z0-9][a-z0-9-]{1,62}\/[a-z0-9][a-z0-9-]{1,62}$/.test(value)) {
|
|
2304
|
+
fail(`Invalid component id "${value}". Expected @project-namespace/component-slug.`, 'component.id_invalid', 2);
|
|
2305
|
+
}
|
|
2306
|
+
return value;
|
|
2307
|
+
}
|
|
2308
|
+
function normalizeProjectNamespace(value) {
|
|
2309
|
+
const normalized = value
|
|
2310
|
+
.trim()
|
|
2311
|
+
.toLowerCase()
|
|
2312
|
+
.replace(/^@/, '')
|
|
2313
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
2314
|
+
.replace(/^-+|-+$/g, '')
|
|
2315
|
+
.slice(0, 63);
|
|
2316
|
+
if (!/^[a-z0-9][a-z0-9-]{1,62}$/.test(normalized)) {
|
|
2317
|
+
fail(`Invalid project namespace "${value}".`, 'project_context.namespace_invalid', 2);
|
|
2318
|
+
}
|
|
2319
|
+
return normalized;
|
|
2320
|
+
}
|
|
2321
|
+
function displayNameFromComponentId(componentId) {
|
|
2322
|
+
const slug = componentId.split('/').at(-1) ?? componentId;
|
|
2323
|
+
return slug
|
|
2324
|
+
.split('-')
|
|
2325
|
+
.filter(Boolean)
|
|
2326
|
+
.map((part) => `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`)
|
|
2327
|
+
.join(' ');
|
|
2328
|
+
}
|
|
1921
2329
|
async function fetchJson(url, init, code) {
|
|
1922
2330
|
const response = await fetch(url, init);
|
|
1923
2331
|
const payload = await response.json().catch(() => ({}));
|
|
@@ -2385,7 +2793,7 @@ function collectPackageFiles(root) {
|
|
|
2385
2793
|
return files.sort((a, b) => a.name.localeCompare(b.name));
|
|
2386
2794
|
}
|
|
2387
2795
|
function buildLockfileEvidenceFile(root) {
|
|
2388
|
-
const lockfiles =
|
|
2796
|
+
const lockfiles = collectPackageManagerLockfileEvidence(root);
|
|
2389
2797
|
if (lockfiles.length === 0)
|
|
2390
2798
|
return undefined;
|
|
2391
2799
|
const packageJson = readFileSync(join(root, 'package.json'));
|
|
@@ -2394,6 +2802,8 @@ function buildLockfileEvidenceFile(root) {
|
|
|
2394
2802
|
packageJsonSha256: sha256Buffer(packageJson),
|
|
2395
2803
|
lockfiles: lockfiles.map((lockfile) => ({
|
|
2396
2804
|
fileName: lockfile.name,
|
|
2805
|
+
source: lockfile.source,
|
|
2806
|
+
relativePath: lockfile.relativePath,
|
|
2397
2807
|
sizeBytes: lockfile.data.byteLength,
|
|
2398
2808
|
sha256: sha256Buffer(lockfile.data),
|
|
2399
2809
|
})),
|
|
@@ -2403,16 +2813,47 @@ function buildLockfileEvidenceFile(root) {
|
|
|
2403
2813
|
data: Buffer.from(`${JSON.stringify(receipt)}\n`, 'utf8'),
|
|
2404
2814
|
};
|
|
2405
2815
|
}
|
|
2406
|
-
function
|
|
2816
|
+
function collectPackageManagerLockfileEvidence(root) {
|
|
2817
|
+
const directLockfiles = collectDirectPackageManagerLockfiles(root);
|
|
2818
|
+
if (directLockfiles.length > 0)
|
|
2819
|
+
return directLockfiles;
|
|
2820
|
+
const workspaceLockfile = findPnpmWorkspaceRootLockfile(root);
|
|
2821
|
+
return workspaceLockfile ? [workspaceLockfile] : [];
|
|
2822
|
+
}
|
|
2823
|
+
function collectDirectPackageManagerLockfiles(root) {
|
|
2407
2824
|
const lockfiles = [];
|
|
2408
2825
|
for (const name of PACKAGE_MANAGER_LOCKFILE_NAMES) {
|
|
2409
2826
|
const full = join(root, name);
|
|
2410
2827
|
if (!existsSync(full))
|
|
2411
2828
|
continue;
|
|
2412
|
-
lockfiles.push({
|
|
2829
|
+
lockfiles.push({
|
|
2830
|
+
name,
|
|
2831
|
+
data: readFileSync(full),
|
|
2832
|
+
source: 'component',
|
|
2833
|
+
relativePath: name,
|
|
2834
|
+
});
|
|
2413
2835
|
}
|
|
2414
2836
|
return lockfiles;
|
|
2415
2837
|
}
|
|
2838
|
+
function findPnpmWorkspaceRootLockfile(root) {
|
|
2839
|
+
let current = resolve(root);
|
|
2840
|
+
while (true) {
|
|
2841
|
+
const workspaceConfig = join(current, 'pnpm-workspace.yaml');
|
|
2842
|
+
const lockfile = join(current, 'pnpm-lock.yaml');
|
|
2843
|
+
if (existsSync(workspaceConfig) && existsSync(lockfile)) {
|
|
2844
|
+
return {
|
|
2845
|
+
name: 'pnpm-lock.yaml',
|
|
2846
|
+
data: readFileSync(lockfile),
|
|
2847
|
+
source: 'workspace_root',
|
|
2848
|
+
relativePath: relative(root, lockfile).replace(/\\/g, '/'),
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
const parent = dirname(current);
|
|
2852
|
+
if (parent === current)
|
|
2853
|
+
return undefined;
|
|
2854
|
+
current = parent;
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2416
2857
|
function sha256Buffer(data) {
|
|
2417
2858
|
return `sha256:${createHash('sha256').update(data).digest('hex')}`;
|
|
2418
2859
|
}
|
|
@@ -2602,6 +3043,26 @@ function printBuildSummary(payload) {
|
|
|
2602
3043
|
console.log(`${stringValue(diagnosticItem?.severity)?.toUpperCase() ?? 'INFO'} ${stringValue(diagnosticItem?.code) ?? 'unknown'}: ${stringValue(diagnosticItem?.message) ?? ''}`);
|
|
2603
3044
|
}
|
|
2604
3045
|
}
|
|
3046
|
+
function failIfBuildFailed(payload) {
|
|
3047
|
+
const status = getBuildStatus(payload);
|
|
3048
|
+
if (!status || !['failed', 'cancelled', 'canceled'].includes(status.toLowerCase()))
|
|
3049
|
+
return;
|
|
3050
|
+
const buildId = getBuildId(payload) ?? 'unknown';
|
|
3051
|
+
const diagnosticItem = firstBuildDiagnostic(payload);
|
|
3052
|
+
const diagnosticText = diagnosticItem
|
|
3053
|
+
? `${stringValue(diagnosticItem.code) ?? 'unknown'}: ${stringValue(diagnosticItem.message) ?? ''}`.trim()
|
|
3054
|
+
: 'no diagnostic';
|
|
3055
|
+
fail(`PromptFrame platform build failed: buildId=${buildId} status=${status} diagnostic=${diagnosticText}`, 'status.build.failed', 1);
|
|
3056
|
+
}
|
|
3057
|
+
function getBuildStatus(payload) {
|
|
3058
|
+
return stringValue(asRecord(payload.build)?.status) ?? stringValue(payload.status);
|
|
3059
|
+
}
|
|
3060
|
+
function firstBuildDiagnostic(payload) {
|
|
3061
|
+
const diagnostics = arrayValue(asRecord(payload.build)?.diagnostics)
|
|
3062
|
+
.map((item) => asRecord(item))
|
|
3063
|
+
.filter((item) => Boolean(item));
|
|
3064
|
+
return diagnostics.find((item) => item.severity === 'error') ?? diagnostics[0];
|
|
3065
|
+
}
|
|
2605
3066
|
function printStatusUrl(endpoint, payload) {
|
|
2606
3067
|
const statusUrl = stringValue(payload.statusUrl) ?? stringValue(asRecord(payload.build)?.statusUrl);
|
|
2607
3068
|
if (!statusUrl)
|
|
@@ -2650,6 +3111,7 @@ Commands:
|
|
|
2650
3111
|
--target <target> marketplace_authoring or project_private_generation
|
|
2651
3112
|
--target marketplace --strict Alias for strict external marketplace authoring
|
|
2652
3113
|
status <buildId> Fetch component build status
|
|
3114
|
+
--fail-on-build-failed Exit nonzero when platform build status is failed/cancelled
|
|
2653
3115
|
reindex <buildId> Rebuild component search/evidence indexes
|
|
2654
3116
|
probe <buildId> --level <level> Rerun component layout/security probe
|
|
2655
3117
|
login --endpoint <url> Start browser login code flow and store a CLI token
|
|
@@ -2657,13 +3119,15 @@ Commands:
|
|
|
2657
3119
|
whoami Show the current PromptFrame CLI identity
|
|
2658
3120
|
logout Revoke the current CLI token and clear local config
|
|
2659
3121
|
discovery Fetch platform endpoint and self-service capabilities
|
|
3122
|
+
init [dir] Write secret-free .promptframerc project context
|
|
2660
3123
|
project list|current List accessible projects or show the active project
|
|
3124
|
+
component list|create List or declare Project-scoped components
|
|
2661
3125
|
ci-token create|list|revoke Manage self-service CI tokens for the current project
|
|
2662
3126
|
setup-ci [dir] --provider github Write a GitHub Actions workflow skeleton
|
|
2663
3127
|
configure --endpoint <url> Write local CLI endpoint/context config
|
|
2664
3128
|
|
|
2665
3129
|
Endpoint resolution:
|
|
2666
|
-
--endpoint, PROMPTFRAME_API_BASE, REMOTION_MEDIA_API_BASE,
|
|
3130
|
+
--endpoint, PROMPTFRAME_API_BASE, REMOTION_MEDIA_API_BASE, local config, then .promptframerc.
|
|
2667
3131
|
The public CLI embeds no production/private endpoint defaults.
|
|
2668
3132
|
Auth context:
|
|
2669
3133
|
promptframe login, PROMPTFRAME_CI_TOKEN, or PROMPTFRAME_CLI_TOKEN for formal endpoints.
|