@promptframe/cli 0.1.30 → 0.1.32
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 +51 -7
- package/dist/index.js +911 -24
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -19,6 +19,21 @@ const DEFAULT_CLI_LOGIN_SCOPES = ['component.upload', 'component.status.read'];
|
|
|
19
19
|
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
|
+
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
|
+
]);
|
|
22
37
|
const PACKAGE_MANAGER_LOCKFILE_NAMES = [
|
|
23
38
|
'package-lock.json',
|
|
24
39
|
'npm-shrinkwrap.json',
|
|
@@ -83,16 +98,26 @@ async function run(name, argv) {
|
|
|
83
98
|
case 'discovery':
|
|
84
99
|
await discovery(argv);
|
|
85
100
|
break;
|
|
101
|
+
case 'init':
|
|
102
|
+
await initProjectContext(argv);
|
|
103
|
+
break;
|
|
86
104
|
case 'project':
|
|
87
105
|
await project(argv);
|
|
88
106
|
break;
|
|
89
107
|
case 'projects':
|
|
90
108
|
await project(['list', ...argv]);
|
|
91
109
|
break;
|
|
110
|
+
case 'component':
|
|
111
|
+
case 'components':
|
|
112
|
+
await component(argv);
|
|
113
|
+
break;
|
|
92
114
|
case 'ci-token':
|
|
93
115
|
case 'token':
|
|
94
116
|
await ciToken(argv);
|
|
95
117
|
break;
|
|
118
|
+
case 'workspace':
|
|
119
|
+
workspace(argv);
|
|
120
|
+
break;
|
|
96
121
|
case 'setup-ci':
|
|
97
122
|
setupCi(argv);
|
|
98
123
|
break;
|
|
@@ -157,7 +182,8 @@ function doctor(argv) {
|
|
|
157
182
|
console.log(`doctor passed: ${dir}`);
|
|
158
183
|
}
|
|
159
184
|
function validate(argv) {
|
|
160
|
-
const
|
|
185
|
+
const input = resolveComponentInput(argv);
|
|
186
|
+
const dir = input.dir;
|
|
161
187
|
const manifest = validateComponentDirectory(dir);
|
|
162
188
|
const publicResources = evaluateDirectoryPublicResources(dir);
|
|
163
189
|
assertPublicResourcesAccepted(publicResources);
|
|
@@ -171,6 +197,7 @@ function validate(argv) {
|
|
|
171
197
|
const output = {
|
|
172
198
|
command: 'validate',
|
|
173
199
|
dir,
|
|
200
|
+
source: input.source,
|
|
174
201
|
manifest: {
|
|
175
202
|
id: manifest.id,
|
|
176
203
|
name: manifest.name,
|
|
@@ -195,7 +222,8 @@ function validate(argv) {
|
|
|
195
222
|
printDiagnostics(diagnostics);
|
|
196
223
|
}
|
|
197
224
|
async function check(argv) {
|
|
198
|
-
const
|
|
225
|
+
const input = resolveComponentInput(argv);
|
|
226
|
+
const dir = input.dir;
|
|
199
227
|
const target = resolveUploadTarget(argv);
|
|
200
228
|
const manifest = validateComponentDirectory(dir);
|
|
201
229
|
assertAuthoringPackageFreshness(dir, target);
|
|
@@ -214,6 +242,7 @@ async function check(argv) {
|
|
|
214
242
|
const output = {
|
|
215
243
|
command: 'check',
|
|
216
244
|
dir,
|
|
245
|
+
source: input.source,
|
|
217
246
|
manifest: manifestSummary(manifest),
|
|
218
247
|
checkedRuleIds: VALIDATE_CHECKED_RULE_IDS,
|
|
219
248
|
securityPolicyVersion: PROMPTFRAME_PUBLIC_SECURITY_POLICY.policyVersion,
|
|
@@ -267,7 +296,8 @@ function upgrade(argv) {
|
|
|
267
296
|
}
|
|
268
297
|
}
|
|
269
298
|
function preview(argv) {
|
|
270
|
-
const
|
|
299
|
+
const input = resolveComponentInput(argv);
|
|
300
|
+
const dir = input.dir;
|
|
271
301
|
const manifest = validateComponentDirectory(dir);
|
|
272
302
|
const previewEnvelope = readPreviewProps(dir);
|
|
273
303
|
const previewScript = resolveLocalPreviewScript(readPackageManifest(join(dir, 'package.json')));
|
|
@@ -282,6 +312,7 @@ function preview(argv) {
|
|
|
282
312
|
const output = {
|
|
283
313
|
command: 'preview',
|
|
284
314
|
dir,
|
|
315
|
+
source: input.source,
|
|
285
316
|
manifest: manifestSummary(manifest),
|
|
286
317
|
renderingSystem: 'remotion',
|
|
287
318
|
previewSource: 'src/preview-props.json',
|
|
@@ -302,7 +333,8 @@ function preview(argv) {
|
|
|
302
333
|
console.log(`Run: npm run ${previewScript}`);
|
|
303
334
|
}
|
|
304
335
|
async function dev(argv) {
|
|
305
|
-
const
|
|
336
|
+
const input = resolveComponentInput(argv);
|
|
337
|
+
const dir = input.dir;
|
|
306
338
|
const target = resolveUploadTarget(argv);
|
|
307
339
|
const manifest = validateComponentDirectory(dir);
|
|
308
340
|
assertAuthoringPackageFreshness(dir, target);
|
|
@@ -315,6 +347,7 @@ async function dev(argv) {
|
|
|
315
347
|
const output = {
|
|
316
348
|
command: 'dev',
|
|
317
349
|
dir,
|
|
350
|
+
source: input.source,
|
|
318
351
|
manifest: manifestSummary(manifest),
|
|
319
352
|
renderingSystem: 'remotion-player',
|
|
320
353
|
previewSource: 'src/preview-props.json',
|
|
@@ -343,13 +376,15 @@ async function dev(argv) {
|
|
|
343
376
|
await runDevServer(dir, devCommand);
|
|
344
377
|
}
|
|
345
378
|
function packageComponent(argv) {
|
|
346
|
-
const
|
|
379
|
+
const input = resolveComponentInput(argv);
|
|
380
|
+
const artifact = packageDirectory(input.dir, valueAfter(argv, '--out'));
|
|
347
381
|
printJson({
|
|
348
382
|
command: 'package',
|
|
349
383
|
diagnostic: diagnostic('package.completed', 'info', 'Component source archive created.'),
|
|
350
384
|
out: artifact.out,
|
|
351
385
|
sizeBytes: artifact.sizeBytes,
|
|
352
386
|
sha256: artifact.sha256,
|
|
387
|
+
source: input.source,
|
|
353
388
|
publicResources: artifact.publicResources,
|
|
354
389
|
});
|
|
355
390
|
}
|
|
@@ -444,18 +479,25 @@ async function remoteCommand(name, argv) {
|
|
|
444
479
|
}
|
|
445
480
|
}
|
|
446
481
|
async function uploadComponent(argv) {
|
|
447
|
-
const
|
|
482
|
+
const workspaceComponent = valueAfter(argv, '--workspace-component');
|
|
483
|
+
const target = resolve(componentPathArg(argv) ?? '.');
|
|
448
484
|
const uploadTarget = resolveUploadTarget(argv);
|
|
449
485
|
let localReusability;
|
|
486
|
+
if (workspaceComponent && target.endsWith('.zip')) {
|
|
487
|
+
fail('upload --workspace-component requires a workspace root, not a zip file.', 'workspace.component.zip_unsupported', 2);
|
|
488
|
+
}
|
|
489
|
+
const input = target.endsWith('.zip')
|
|
490
|
+
? { dir: target, source: { mode: 'zip' } }
|
|
491
|
+
: resolveComponentInput(argv);
|
|
450
492
|
const artifact = target.endsWith('.zip')
|
|
451
493
|
? packageZipForUpload(target, uploadTarget)
|
|
452
|
-
: packageDirectoryForUploadWithLocalReusability(
|
|
494
|
+
: packageDirectoryForUploadWithLocalReusability(input.dir, uploadTarget, valueAfter(argv, '--out'), (reusability) => {
|
|
453
495
|
localReusability = reusability;
|
|
454
496
|
});
|
|
455
497
|
const localDiagnostics = localReusability
|
|
456
498
|
? [
|
|
457
499
|
...reusabilityDiagnostics(localReusability),
|
|
458
|
-
...stylePropDiagnostics(
|
|
500
|
+
...stylePropDiagnostics(input.dir),
|
|
459
501
|
...(artifact.publicResources?.diagnostics ?? []),
|
|
460
502
|
]
|
|
461
503
|
: undefined;
|
|
@@ -470,19 +512,22 @@ async function uploadComponent(argv) {
|
|
|
470
512
|
headers: {
|
|
471
513
|
...buildRemoteHeaders(endpoint, argv),
|
|
472
514
|
...buildSecurityPolicyHeaders(),
|
|
515
|
+
...buildSourceMetadataHeaders(input.source),
|
|
473
516
|
'x-promptframe-upload-target': uploadTarget,
|
|
474
517
|
},
|
|
475
518
|
}, 'upload.http.failed');
|
|
476
|
-
const
|
|
477
|
-
|
|
519
|
+
const safePayload = sanitizePublicUploadPayload(payload);
|
|
520
|
+
const output = sanitizePublicUploadPayload({
|
|
521
|
+
...safePayload,
|
|
478
522
|
command: 'upload',
|
|
479
523
|
endpoint,
|
|
480
524
|
uploadTarget,
|
|
481
525
|
jobId: getBuildId(payload),
|
|
482
526
|
package: artifact,
|
|
527
|
+
source: input.source,
|
|
483
528
|
...(localReusability ? { localReusability, diagnostics: localDiagnostics } : {}),
|
|
484
529
|
diagnostic: diagnostic('upload.completed', 'info', 'Component upload accepted by platform.'),
|
|
485
|
-
};
|
|
530
|
+
});
|
|
486
531
|
if (hasFlag(argv, '--json')) {
|
|
487
532
|
printJson(output);
|
|
488
533
|
return;
|
|
@@ -490,10 +535,56 @@ async function uploadComponent(argv) {
|
|
|
490
535
|
console.log('Upload accepted by PromptFrame platform.');
|
|
491
536
|
console.log(`Build: ${output.jobId ?? 'unknown'}`);
|
|
492
537
|
console.log(`Status: ${stringValue(payload.status) ?? stringValue(asRecord(payload.build)?.status) ?? 'queued'}`);
|
|
538
|
+
if (output.jobId) {
|
|
539
|
+
console.log(`Next: promptframe status ${output.jobId} --endpoint ${endpoint}`);
|
|
540
|
+
}
|
|
493
541
|
if (localDiagnostics)
|
|
494
542
|
printDiagnostics(localDiagnostics);
|
|
495
543
|
printStatusUrl(endpoint, payload);
|
|
496
544
|
}
|
|
545
|
+
const PUBLIC_UPLOAD_JSON_OMIT_KEYS = new Set([
|
|
546
|
+
'providerUsageReceipt',
|
|
547
|
+
'providerUsageReceipts',
|
|
548
|
+
'vectorRef',
|
|
549
|
+
'retryKey',
|
|
550
|
+
'sourceHash',
|
|
551
|
+
'sourceHashDetails',
|
|
552
|
+
'internalSourceHash',
|
|
553
|
+
'schemaHash',
|
|
554
|
+
'manifestHash',
|
|
555
|
+
'bundleHash',
|
|
556
|
+
'evidence',
|
|
557
|
+
'evidenceRecords',
|
|
558
|
+
'evidenceItems',
|
|
559
|
+
'rawEvidence',
|
|
560
|
+
]);
|
|
561
|
+
function sanitizePublicUploadPayload(value) {
|
|
562
|
+
return asRecord(sanitizePublicUploadValue(value)) ?? {};
|
|
563
|
+
}
|
|
564
|
+
function sanitizePublicUploadValue(value) {
|
|
565
|
+
if (typeof value === 'string') {
|
|
566
|
+
return scrubInternalReferences(value);
|
|
567
|
+
}
|
|
568
|
+
if (Array.isArray(value)) {
|
|
569
|
+
return value.map((item) => sanitizePublicUploadValue(item));
|
|
570
|
+
}
|
|
571
|
+
const record = asRecord(value);
|
|
572
|
+
if (!record)
|
|
573
|
+
return value;
|
|
574
|
+
const output = {};
|
|
575
|
+
for (const [key, entry] of Object.entries(record)) {
|
|
576
|
+
if (PUBLIC_UPLOAD_JSON_OMIT_KEYS.has(key))
|
|
577
|
+
continue;
|
|
578
|
+
output[key] = sanitizePublicUploadValue(entry);
|
|
579
|
+
}
|
|
580
|
+
return output;
|
|
581
|
+
}
|
|
582
|
+
function scrubInternalReferences(value) {
|
|
583
|
+
return value
|
|
584
|
+
.replace(/\bREQ-\d+\b/g, 'internal reference')
|
|
585
|
+
.replace(/\bTASK-\d+(?:-[A-Za-z0-9-]+)?\b/g, 'internal task')
|
|
586
|
+
.replace(/\bBUG-\d+\b/g, 'internal bug');
|
|
587
|
+
}
|
|
497
588
|
function resolveUploadTarget(argv) {
|
|
498
589
|
const raw = normalizeUploadTargetAlias(valueAfter(argv, '--target') ?? valueAfter(argv, '--upload-target') ?? 'marketplace_authoring');
|
|
499
590
|
const parsed = authoringUploadTargetSchema.safeParse(raw);
|
|
@@ -573,6 +664,331 @@ function buildFreshnessWarningDecision(target, code, message) {
|
|
|
573
664
|
function formatRemoteStandardStaleFailure(remoteSourceHash) {
|
|
574
665
|
return `PromptFrame component standard is stale: local=${COMPONENT_STANDARD_SOURCE_HASH}, platform=${remoteSourceHash}. Run promptframe upgrade . --apply before upload.`;
|
|
575
666
|
}
|
|
667
|
+
function workspace(argv) {
|
|
668
|
+
const subcommand = argv[0] ?? 'validate';
|
|
669
|
+
const rest = argv.slice(argv[0] ? 1 : 0);
|
|
670
|
+
switch (subcommand) {
|
|
671
|
+
case 'validate':
|
|
672
|
+
workspaceValidate(rest);
|
|
673
|
+
break;
|
|
674
|
+
case 'list':
|
|
675
|
+
workspaceList(rest);
|
|
676
|
+
break;
|
|
677
|
+
case 'init':
|
|
678
|
+
workspaceInit(rest);
|
|
679
|
+
break;
|
|
680
|
+
case 'add':
|
|
681
|
+
workspaceAdd(rest);
|
|
682
|
+
break;
|
|
683
|
+
default:
|
|
684
|
+
fail(`Unknown workspace command: ${subcommand}`, 'workspace.command.unknown', 2);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
function workspaceValidate(argv) {
|
|
688
|
+
const root = resolve(componentPathArg(argv) ?? '.');
|
|
689
|
+
const reports = collectWorkspaceComponentReports(root);
|
|
690
|
+
const output = {
|
|
691
|
+
command: 'workspace.validate',
|
|
692
|
+
workspace: workspaceSummary(root),
|
|
693
|
+
components: reports.map(workspaceReportForOutput),
|
|
694
|
+
diagnostic: diagnostic('workspace.validate.completed', 'info', 'PromptFrame workspace configuration is valid.'),
|
|
695
|
+
};
|
|
696
|
+
if (hasFlag(argv, '--json')) {
|
|
697
|
+
printJson(output);
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
console.log(`workspace valid: ${root}`);
|
|
701
|
+
for (const component of reports) {
|
|
702
|
+
console.log(`${component.id} -> ${component.path}`);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function workspaceList(argv) {
|
|
706
|
+
const root = resolve(componentPathArg(argv) ?? '.');
|
|
707
|
+
const reports = collectWorkspaceComponentReports(root);
|
|
708
|
+
const output = {
|
|
709
|
+
command: 'workspace.list',
|
|
710
|
+
workspace: workspaceSummary(root),
|
|
711
|
+
components: reports.map(workspaceReportForOutput),
|
|
712
|
+
diagnostic: diagnostic('workspace.list.completed', 'info', 'PromptFrame workspace components listed.'),
|
|
713
|
+
};
|
|
714
|
+
if (hasFlag(argv, '--json')) {
|
|
715
|
+
printJson(output);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
for (const component of reports) {
|
|
719
|
+
console.log(`${component.id}\t${component.path}`);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function workspaceInit(argv) {
|
|
723
|
+
const root = resolve(componentPathArg(argv) ?? '.');
|
|
724
|
+
const configPath = workspaceConfigPath(root);
|
|
725
|
+
if (existsSync(configPath) && !hasFlag(argv, '--force')) {
|
|
726
|
+
fail(`PromptFrame workspace config already exists at ${configPath}. Use --force to overwrite.`, 'workspace.config.exists');
|
|
727
|
+
}
|
|
728
|
+
const componentPath = valueAfter(argv, '--component-path');
|
|
729
|
+
const componentId = valueAfter(argv, '--id');
|
|
730
|
+
const components = componentPath || componentId
|
|
731
|
+
? [{
|
|
732
|
+
id: componentId ?? fail('--id is required when --component-path is provided.', 'workspace.component.id_missing', 2),
|
|
733
|
+
path: normalizeWorkspacePath(componentPath ?? fail('--component-path is required when --id is provided.', 'workspace.component.path_missing', 2)),
|
|
734
|
+
}]
|
|
735
|
+
: [];
|
|
736
|
+
writeWorkspaceConfig(root, components);
|
|
737
|
+
const output = {
|
|
738
|
+
command: 'workspace.init',
|
|
739
|
+
workspace: workspaceSummary(root),
|
|
740
|
+
components,
|
|
741
|
+
diagnostic: diagnostic('workspace.init.completed', 'info', 'PromptFrame workspace configuration written.'),
|
|
742
|
+
};
|
|
743
|
+
if (hasFlag(argv, '--json')) {
|
|
744
|
+
printJson(output);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
console.log(`Wrote PromptFrame workspace config: ${configPath}`);
|
|
748
|
+
}
|
|
749
|
+
function workspaceAdd(argv) {
|
|
750
|
+
const positionals = positionalArgs(argv);
|
|
751
|
+
const root = resolve(positionals.length >= 2 ? positionals[0] : '.');
|
|
752
|
+
const rawPath = positionals.length >= 2 ? positionals[1] : positionals[0];
|
|
753
|
+
const componentPath = normalizeWorkspacePath(rawPath ?? fail('workspace add requires a component path.', 'workspace.component.path_missing', 2));
|
|
754
|
+
const id = valueAfter(argv, '--id') ?? fail('workspace add requires --id <component-id>.', 'workspace.component.id_missing', 2);
|
|
755
|
+
const current = readWorkspaceConfig(root);
|
|
756
|
+
const components = [...current.components, { id, path: componentPath }];
|
|
757
|
+
assertNoDuplicateWorkspaceComponents(components);
|
|
758
|
+
writeWorkspaceConfig(root, components);
|
|
759
|
+
const output = {
|
|
760
|
+
command: 'workspace.add',
|
|
761
|
+
workspace: workspaceSummary(root),
|
|
762
|
+
component: { id, path: componentPath },
|
|
763
|
+
diagnostic: diagnostic('workspace.add.completed', 'info', 'PromptFrame workspace component added.'),
|
|
764
|
+
};
|
|
765
|
+
if (hasFlag(argv, '--json')) {
|
|
766
|
+
printJson(output);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
console.log(`Added workspace component: ${id} -> ${componentPath}`);
|
|
770
|
+
}
|
|
771
|
+
function resolveComponentInput(argv) {
|
|
772
|
+
const workspaceComponentId = valueAfter(argv, '--workspace-component');
|
|
773
|
+
if (!workspaceComponentId) {
|
|
774
|
+
return {
|
|
775
|
+
dir: resolve(componentPathArg(argv) ?? '.'),
|
|
776
|
+
source: {
|
|
777
|
+
mode: 'single_component',
|
|
778
|
+
componentPath: '.',
|
|
779
|
+
},
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
const root = resolve(componentPathArg(argv) ?? '.');
|
|
783
|
+
const report = resolveWorkspaceComponent(root, workspaceComponentId);
|
|
784
|
+
return {
|
|
785
|
+
dir: report.absolutePath,
|
|
786
|
+
source: {
|
|
787
|
+
mode: 'workspace',
|
|
788
|
+
workspaceConfig: 'promptframe-workspace.json',
|
|
789
|
+
workspaceComponentId: report.id,
|
|
790
|
+
componentPath: report.path,
|
|
791
|
+
manifestId: stringValue(report.manifest.id) ?? report.id,
|
|
792
|
+
},
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function resolveWorkspaceComponent(root, componentId) {
|
|
796
|
+
const reports = collectWorkspaceComponentReports(root);
|
|
797
|
+
const report = reports.find((component) => component.id === componentId);
|
|
798
|
+
if (!report) {
|
|
799
|
+
fail(`PromptFrame workspace component not found: ${componentId}. Run promptframe workspace list ${root} to inspect configured ids.`, 'workspace.component.not_found', 2);
|
|
800
|
+
}
|
|
801
|
+
return report;
|
|
802
|
+
}
|
|
803
|
+
function collectWorkspaceComponentReports(root) {
|
|
804
|
+
const workspace = readWorkspaceConfig(root);
|
|
805
|
+
if (workspace.components.length === 0) {
|
|
806
|
+
fail('PromptFrame workspace config does not list any components.', 'workspace.components.empty', 2);
|
|
807
|
+
}
|
|
808
|
+
return workspace.components.map((component) => {
|
|
809
|
+
const absolutePath = resolve(root, component.path);
|
|
810
|
+
assertWorkspacePathInsideRoot(root, absolutePath, component.path);
|
|
811
|
+
if (!existsSync(absolutePath)) {
|
|
812
|
+
fail(`PromptFrame workspace component path does not exist: ${component.path}`, 'workspace.component.path_missing', 2);
|
|
813
|
+
}
|
|
814
|
+
const manifest = validateComponentDirectory(absolutePath);
|
|
815
|
+
if (manifest.id !== component.id) {
|
|
816
|
+
fail(`PromptFrame workspace component ${component.id} points to ${component.path}, but manifest.json declares ${manifest.id}. Keep promptframe-workspace.json and manifest.json ids identical.`, 'workspace.component.manifest_id_mismatch');
|
|
817
|
+
}
|
|
818
|
+
return {
|
|
819
|
+
...component,
|
|
820
|
+
absolutePath,
|
|
821
|
+
manifest: manifestSummary(manifest),
|
|
822
|
+
};
|
|
823
|
+
});
|
|
824
|
+
}
|
|
825
|
+
function workspaceReportForOutput(report) {
|
|
826
|
+
return {
|
|
827
|
+
id: report.id,
|
|
828
|
+
path: report.path,
|
|
829
|
+
manifest: report.manifest,
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function workspaceSummary(root) {
|
|
833
|
+
return {
|
|
834
|
+
schemaVersion: PROMPTFRAME_WORKSPACE_SCHEMA_VERSION,
|
|
835
|
+
root,
|
|
836
|
+
configPath: workspaceConfigPath(root),
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function workspaceConfigPath(root) {
|
|
840
|
+
return join(root, 'promptframe-workspace.json');
|
|
841
|
+
}
|
|
842
|
+
function readWorkspaceConfig(root) {
|
|
843
|
+
const configPath = workspaceConfigPath(root);
|
|
844
|
+
if (!existsSync(configPath)) {
|
|
845
|
+
fail(`PromptFrame workspace config not found at ${configPath}.`, 'workspace.config.missing', 2);
|
|
846
|
+
}
|
|
847
|
+
let raw;
|
|
848
|
+
try {
|
|
849
|
+
raw = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
850
|
+
}
|
|
851
|
+
catch {
|
|
852
|
+
fail(`PromptFrame workspace config must be valid JSON: ${configPath}`, 'workspace.config.invalid_json', 2);
|
|
853
|
+
}
|
|
854
|
+
const config = asRecord(raw);
|
|
855
|
+
if (!config) {
|
|
856
|
+
fail('PromptFrame workspace config must be a JSON object.', 'workspace.config.invalid', 2);
|
|
857
|
+
}
|
|
858
|
+
const schemaVersion = stringValue(config.schemaVersion);
|
|
859
|
+
if (schemaVersion !== PROMPTFRAME_WORKSPACE_SCHEMA_VERSION) {
|
|
860
|
+
fail(`Unsupported PromptFrame workspace schemaVersion: ${schemaVersion ?? '<missing>'}. Expected ${PROMPTFRAME_WORKSPACE_SCHEMA_VERSION}.`, 'workspace.config.schema_version_unsupported', 2);
|
|
861
|
+
}
|
|
862
|
+
const rawComponents = arrayValue(config.components);
|
|
863
|
+
const components = rawComponents.map((item, index) => {
|
|
864
|
+
const component = asRecord(item);
|
|
865
|
+
if (!component) {
|
|
866
|
+
fail(`PromptFrame workspace components[${index}] must be a JSON object.`, 'workspace.component.invalid', 2);
|
|
867
|
+
}
|
|
868
|
+
const id = stringValue(component.id);
|
|
869
|
+
const path = stringValue(component.path);
|
|
870
|
+
if (!id) {
|
|
871
|
+
fail(`PromptFrame workspace components[${index}].id is required.`, 'workspace.component.id_missing', 2);
|
|
872
|
+
}
|
|
873
|
+
if (!path) {
|
|
874
|
+
fail(`PromptFrame workspace components[${index}].path is required.`, 'workspace.component.path_missing', 2);
|
|
875
|
+
}
|
|
876
|
+
return {
|
|
877
|
+
id,
|
|
878
|
+
path: normalizeWorkspacePath(path),
|
|
879
|
+
};
|
|
880
|
+
});
|
|
881
|
+
assertNoDuplicateWorkspaceComponents(components);
|
|
882
|
+
return { schemaVersion, components };
|
|
883
|
+
}
|
|
884
|
+
function writeWorkspaceConfig(root, components) {
|
|
885
|
+
assertNoDuplicateWorkspaceComponents(components);
|
|
886
|
+
mkdirSync(root, { recursive: true });
|
|
887
|
+
writeFileSync(workspaceConfigPath(root), `${JSON.stringify({
|
|
888
|
+
schemaVersion: PROMPTFRAME_WORKSPACE_SCHEMA_VERSION,
|
|
889
|
+
components,
|
|
890
|
+
}, null, 2)}\n`, 'utf8');
|
|
891
|
+
}
|
|
892
|
+
function assertNoDuplicateWorkspaceComponents(components) {
|
|
893
|
+
const ids = new Set();
|
|
894
|
+
const paths = new Set();
|
|
895
|
+
for (const component of components) {
|
|
896
|
+
if (ids.has(component.id)) {
|
|
897
|
+
fail(`Duplicate PromptFrame workspace component id: ${component.id}`, 'workspace.component.id_duplicate', 2);
|
|
898
|
+
}
|
|
899
|
+
if (paths.has(component.path)) {
|
|
900
|
+
fail(`Duplicate PromptFrame workspace component path: ${component.path}`, 'workspace.component.path_duplicate', 2);
|
|
901
|
+
}
|
|
902
|
+
ids.add(component.id);
|
|
903
|
+
paths.add(component.path);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function normalizeWorkspacePath(rawPath) {
|
|
907
|
+
const normalized = rawPath
|
|
908
|
+
.replaceAll('\\', '/')
|
|
909
|
+
.replace(/^\.\/+/, '')
|
|
910
|
+
.replace(/\/+$/, '');
|
|
911
|
+
if (!normalized) {
|
|
912
|
+
fail('PromptFrame workspace component path is required.', 'workspace.component.path_missing', 2);
|
|
913
|
+
}
|
|
914
|
+
if (normalized.startsWith('/') || /^[A-Za-z]:\//.test(normalized)) {
|
|
915
|
+
fail(`PromptFrame workspace component path must be relative: ${rawPath}`, 'workspace.component.path_absolute', 2);
|
|
916
|
+
}
|
|
917
|
+
const segments = normalized.split('/');
|
|
918
|
+
if (segments.some((segment) => !segment || segment === '.' || segment === '..')) {
|
|
919
|
+
fail(`PromptFrame workspace component path must not contain empty, "." or ".." segments: ${rawPath}`, 'workspace.component.path_unsafe', 2);
|
|
920
|
+
}
|
|
921
|
+
return normalized;
|
|
922
|
+
}
|
|
923
|
+
function assertWorkspacePathInsideRoot(root, absolutePath, displayPath) {
|
|
924
|
+
const relativePath = relative(root, absolutePath);
|
|
925
|
+
if (relativePath.startsWith('..') || relativePath === '' || /^[A-Za-z]:/.test(relativePath)) {
|
|
926
|
+
fail(`PromptFrame workspace component path must stay inside the workspace root: ${displayPath}`, 'workspace.component.path_unsafe', 2);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
function buildSourceMetadataHeaders(source) {
|
|
930
|
+
if (source.mode !== 'workspace')
|
|
931
|
+
return {};
|
|
932
|
+
return {
|
|
933
|
+
'x-promptframe-source-mode': 'workspace',
|
|
934
|
+
'x-promptframe-source-workspace-config': source.workspaceConfig,
|
|
935
|
+
'x-promptframe-source-workspace-component-id': source.workspaceComponentId,
|
|
936
|
+
'x-promptframe-source-component-path': source.componentPath,
|
|
937
|
+
'x-promptframe-source-manifest-id': source.manifestId,
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
const POSITIONAL_VALUE_FLAGS = new Set([
|
|
941
|
+
'--allowed-upload-target',
|
|
942
|
+
'--auth-permissions',
|
|
943
|
+
'--auth-roles',
|
|
944
|
+
'--component-path',
|
|
945
|
+
'--component-id',
|
|
946
|
+
'--config',
|
|
947
|
+
'--description',
|
|
948
|
+
'--display-name',
|
|
949
|
+
'--endpoint',
|
|
950
|
+
'--expires-at',
|
|
951
|
+
'--host',
|
|
952
|
+
'--id',
|
|
953
|
+
'--level',
|
|
954
|
+
'--name',
|
|
955
|
+
'--out',
|
|
956
|
+
'--poll-interval-seconds',
|
|
957
|
+
'--port',
|
|
958
|
+
'--provider',
|
|
959
|
+
'--provider-kind',
|
|
960
|
+
'--provider-name',
|
|
961
|
+
'--project-id',
|
|
962
|
+
'--project-context',
|
|
963
|
+
'--project-namespace',
|
|
964
|
+
'--reason',
|
|
965
|
+
'--scope',
|
|
966
|
+
'--session-id',
|
|
967
|
+
'--target',
|
|
968
|
+
'--tenant-id',
|
|
969
|
+
'--timeout-seconds',
|
|
970
|
+
'--token',
|
|
971
|
+
'--upload-target',
|
|
972
|
+
'--user-id',
|
|
973
|
+
'--workspace-config',
|
|
974
|
+
'--workspace-component',
|
|
975
|
+
]);
|
|
976
|
+
function componentPathArg(argv) {
|
|
977
|
+
return positionalArgs(argv)[0];
|
|
978
|
+
}
|
|
979
|
+
function positionalArgs(argv) {
|
|
980
|
+
const result = [];
|
|
981
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
982
|
+
const arg = argv[index];
|
|
983
|
+
if (arg.startsWith('--')) {
|
|
984
|
+
if (!arg.includes('=') && POSITIONAL_VALUE_FLAGS.has(arg))
|
|
985
|
+
index += 1;
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
result.push(arg);
|
|
989
|
+
}
|
|
990
|
+
return result;
|
|
991
|
+
}
|
|
576
992
|
function extractRemoteStandardSourceHash(payload) {
|
|
577
993
|
return stringValue(payload.sourceHash)
|
|
578
994
|
?? stringValue(payload.standardSourceHash)
|
|
@@ -593,9 +1009,13 @@ async function showStatus(argv) {
|
|
|
593
1009
|
};
|
|
594
1010
|
if (hasFlag(argv, '--json')) {
|
|
595
1011
|
printJson(output);
|
|
596
|
-
return;
|
|
597
1012
|
}
|
|
598
|
-
|
|
1013
|
+
else {
|
|
1014
|
+
printBuildSummary(output);
|
|
1015
|
+
}
|
|
1016
|
+
if (hasFlag(argv, '--fail-on-build-failed')) {
|
|
1017
|
+
failIfBuildFailed(output);
|
|
1018
|
+
}
|
|
599
1019
|
}
|
|
600
1020
|
async function reindexEvidence(argv) {
|
|
601
1021
|
const buildId = requiredArg(argv[0], 'reindex requires a build id', 'reindex.build_id.missing');
|
|
@@ -817,6 +1237,54 @@ async function discovery(argv) {
|
|
|
817
1237
|
if (uploadTargets.length > 0)
|
|
818
1238
|
console.log(`Upload targets: ${uploadTargets.join(', ')}`);
|
|
819
1239
|
}
|
|
1240
|
+
async function initProjectContext(argv) {
|
|
1241
|
+
const root = resolve(componentPathArg(argv) ?? '.');
|
|
1242
|
+
const contextPath = join(root, PROMPTFRAME_PROJECT_CONTEXT_FILE_NAME);
|
|
1243
|
+
if (existsSync(contextPath) && !hasFlag(argv, '--force')) {
|
|
1244
|
+
fail(`PromptFrame project context already exists at ${contextPath}. Use --force to overwrite.`, 'project_context.exists', 2);
|
|
1245
|
+
}
|
|
1246
|
+
const endpoint = resolveEndpoint('init', argv);
|
|
1247
|
+
const payload = await fetchProjects(endpoint, argv);
|
|
1248
|
+
const projects = arrayValue(payload.projects).map((item) => asRecord(item)).filter((item) => Boolean(item));
|
|
1249
|
+
const requestedProjectId = valueAfter(argv, '--project-id');
|
|
1250
|
+
const currentProjectId = requestedProjectId ?? stringValue(payload.currentProjectId);
|
|
1251
|
+
const project = projects.find((item) => stringValue(item.projectId) === currentProjectId || (!requestedProjectId && item.isCurrent === true));
|
|
1252
|
+
if (!project) {
|
|
1253
|
+
fail(requestedProjectId
|
|
1254
|
+
? `PromptFrame project ${requestedProjectId} is not available for this credential.`
|
|
1255
|
+
: 'PromptFrame did not report an active project for this credential.', 'project_context.project_missing', 2);
|
|
1256
|
+
}
|
|
1257
|
+
const projectId = requiredArg(stringValue(project.projectId), 'PromptFrame project response did not include projectId.', 'project_context.project_id_missing');
|
|
1258
|
+
const tenantId = stringValue(project.tenantId);
|
|
1259
|
+
const context = buildProjectContext({
|
|
1260
|
+
endpoint,
|
|
1261
|
+
tenantId,
|
|
1262
|
+
projectId,
|
|
1263
|
+
projectNamespace: valueAfter(argv, '--project-namespace')
|
|
1264
|
+
?? stringValue(project.projectNamespace)
|
|
1265
|
+
?? normalizeProjectNamespace(projectId),
|
|
1266
|
+
defaultUploadTarget: valueAfter(argv, '--target')
|
|
1267
|
+
?? valueAfter(argv, '--upload-target')
|
|
1268
|
+
?? 'marketplace_authoring',
|
|
1269
|
+
workspaceConfig: valueAfter(argv, '--workspace-config') ?? 'promptframe-workspace.json',
|
|
1270
|
+
});
|
|
1271
|
+
mkdirSync(root, { recursive: true });
|
|
1272
|
+
writeFileSync(contextPath, `${JSON.stringify(context, null, 2)}\n`, 'utf8');
|
|
1273
|
+
const output = {
|
|
1274
|
+
command: 'init',
|
|
1275
|
+
endpoint,
|
|
1276
|
+
contextPath,
|
|
1277
|
+
context,
|
|
1278
|
+
project,
|
|
1279
|
+
diagnostic: diagnostic('project_context.init.completed', 'info', 'PromptFrame project context written.'),
|
|
1280
|
+
};
|
|
1281
|
+
if (hasFlag(argv, '--json')) {
|
|
1282
|
+
printJson(output);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
console.log(`Wrote PromptFrame project context: ${contextPath}`);
|
|
1286
|
+
console.log(`Project: ${projectId}`);
|
|
1287
|
+
}
|
|
820
1288
|
async function project(argv) {
|
|
821
1289
|
const subcommand = argv[0] && !argv[0].startsWith('--') ? argv[0] : 'list';
|
|
822
1290
|
if (subcommand !== 'list' && subcommand !== 'current') {
|
|
@@ -873,6 +1341,84 @@ async function fetchProjects(endpoint, argv) {
|
|
|
873
1341
|
headers: buildRemoteHeaders(endpoint, argv),
|
|
874
1342
|
}, 'project.http.failed');
|
|
875
1343
|
}
|
|
1344
|
+
async function component(argv) {
|
|
1345
|
+
const subcommand = argv[0] && !argv[0].startsWith('--') ? argv[0] : 'list';
|
|
1346
|
+
const rest = argv.slice(argv[0] && !argv[0].startsWith('--') ? 1 : 0);
|
|
1347
|
+
switch (subcommand) {
|
|
1348
|
+
case 'list':
|
|
1349
|
+
await componentList(rest);
|
|
1350
|
+
break;
|
|
1351
|
+
case 'create':
|
|
1352
|
+
await componentCreate(rest);
|
|
1353
|
+
break;
|
|
1354
|
+
default:
|
|
1355
|
+
fail(`Unknown component command: ${subcommand}`, 'component.command.unknown', 2);
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
async function componentList(argv) {
|
|
1359
|
+
const endpoint = resolveEndpoint('component', argv);
|
|
1360
|
+
const params = new URLSearchParams();
|
|
1361
|
+
const componentId = valueAfter(argv, '--component-id') ?? positionalArgs(argv)[0];
|
|
1362
|
+
if (componentId)
|
|
1363
|
+
params.set('componentId', assertComponentId(componentId));
|
|
1364
|
+
const query = params.toString();
|
|
1365
|
+
const payload = await fetchJson(`${endpoint}/components/marketplace/self/components${query ? `?${query}` : ''}`, {
|
|
1366
|
+
headers: buildRemoteHeaders(endpoint, argv),
|
|
1367
|
+
}, 'component.list.http.failed');
|
|
1368
|
+
const output = {
|
|
1369
|
+
...payload,
|
|
1370
|
+
command: 'component.list',
|
|
1371
|
+
endpoint,
|
|
1372
|
+
diagnostic: diagnostic('component.list.completed', 'info', 'PromptFrame project components listed.'),
|
|
1373
|
+
};
|
|
1374
|
+
if (hasFlag(argv, '--json')) {
|
|
1375
|
+
printJson(output);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
for (const item of arrayValue(payload.declarations)) {
|
|
1379
|
+
const declaration = asRecord(item);
|
|
1380
|
+
if (declaration)
|
|
1381
|
+
printComponentDeclarationLine(declaration);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
async function componentCreate(argv) {
|
|
1385
|
+
const endpoint = resolveEndpoint('component', argv);
|
|
1386
|
+
const componentId = assertComponentId(requiredArg(positionalArgs(argv)[0], 'component create requires <component-id>.', 'component.id_missing'));
|
|
1387
|
+
const displayName = requiredArg(valueAfter(argv, '--display-name') ?? valueAfter(argv, '--name') ?? displayNameFromComponentId(componentId), 'component create requires --display-name <name>.', 'component.display_name_missing');
|
|
1388
|
+
const description = valueAfter(argv, '--description');
|
|
1389
|
+
const payload = compactRecord({
|
|
1390
|
+
componentId,
|
|
1391
|
+
displayName,
|
|
1392
|
+
description,
|
|
1393
|
+
});
|
|
1394
|
+
const response = await fetchJson(`${endpoint}/components/marketplace/self/components`, {
|
|
1395
|
+
method: 'POST',
|
|
1396
|
+
headers: {
|
|
1397
|
+
...buildRemoteHeaders(endpoint, argv),
|
|
1398
|
+
'content-type': 'application/json',
|
|
1399
|
+
},
|
|
1400
|
+
body: JSON.stringify(payload),
|
|
1401
|
+
}, 'component.create.http.failed');
|
|
1402
|
+
const output = {
|
|
1403
|
+
...response,
|
|
1404
|
+
command: 'component.create',
|
|
1405
|
+
endpoint,
|
|
1406
|
+
diagnostic: diagnostic('component.create.completed', 'info', 'PromptFrame project component declared.'),
|
|
1407
|
+
};
|
|
1408
|
+
if (hasFlag(argv, '--json')) {
|
|
1409
|
+
printJson(output);
|
|
1410
|
+
return;
|
|
1411
|
+
}
|
|
1412
|
+
const declaration = asRecord(response.declaration);
|
|
1413
|
+
if (declaration)
|
|
1414
|
+
printComponentDeclarationLine(declaration);
|
|
1415
|
+
}
|
|
1416
|
+
function printComponentDeclarationLine(declaration) {
|
|
1417
|
+
const componentId = stringValue(declaration.componentId) ?? 'unknown';
|
|
1418
|
+
const status = stringValue(declaration.status) ?? 'unknown';
|
|
1419
|
+
const displayName = stringValue(declaration.displayName) ?? componentId;
|
|
1420
|
+
console.log(`${componentId}\t${status}\t${displayName}`);
|
|
1421
|
+
}
|
|
876
1422
|
function printProjectLine(project, current) {
|
|
877
1423
|
const prefix = current ? '*' : '-';
|
|
878
1424
|
const projectId = stringValue(project.projectId) ?? 'unknown';
|
|
@@ -1068,16 +1614,22 @@ function setupCi(argv) {
|
|
|
1068
1614
|
fail(`Unsupported CI provider: ${provider}.`, 'setup_ci.provider.unsupported', 2);
|
|
1069
1615
|
}
|
|
1070
1616
|
const targetDir = resolve(setupCiTargetDir(argv));
|
|
1071
|
-
const
|
|
1617
|
+
const workspaceMode = hasFlag(argv, '--workspace');
|
|
1618
|
+
const workspaceReports = workspaceMode ? collectWorkspaceComponentReports(targetDir) : [];
|
|
1619
|
+
const workflowPath = join(targetDir, '.github', 'workflows', workspaceMode ? 'promptframe-workspace.yml' : 'promptframe-component.yml');
|
|
1072
1620
|
if (existsSync(workflowPath) && !hasFlag(argv, '--force')) {
|
|
1073
1621
|
fail(`PromptFrame GitHub workflow already exists at ${workflowPath}. Use --force to overwrite.`, 'setup_ci.workflow.exists');
|
|
1074
1622
|
}
|
|
1075
1623
|
mkdirSync(dirname(workflowPath), { recursive: true });
|
|
1076
|
-
writeFileSync(workflowPath, promptFrameGithubWorkflow(), 'utf8');
|
|
1624
|
+
writeFileSync(workflowPath, workspaceMode ? promptFrameGithubWorkspaceWorkflow(workspaceReports) : promptFrameGithubWorkflow(), 'utf8');
|
|
1077
1625
|
const output = {
|
|
1078
1626
|
success: true,
|
|
1079
1627
|
command: 'setup-ci',
|
|
1080
1628
|
provider,
|
|
1629
|
+
...(workspaceMode ? {
|
|
1630
|
+
workspace: true,
|
|
1631
|
+
components: workspaceReports.map(({ id, path }) => ({ id, path })),
|
|
1632
|
+
} : {}),
|
|
1081
1633
|
workflowPath,
|
|
1082
1634
|
requiredSecrets: ['PROMPTFRAME_CI_TOKEN'],
|
|
1083
1635
|
requiredVariables: ['PROMPTFRAME_API_BASE'],
|
|
@@ -1104,6 +1656,186 @@ function setupCiTargetDir(argv) {
|
|
|
1104
1656
|
}
|
|
1105
1657
|
return '.';
|
|
1106
1658
|
}
|
|
1659
|
+
function promptFrameGithubWorkspaceWorkflow(components) {
|
|
1660
|
+
const apiBaseVariable = '$' + '{{ vars.PROMPTFRAME_API_BASE }}';
|
|
1661
|
+
const ciTokenSecret = '$' + '{{ secrets.PROMPTFRAME_CI_TOKEN }}';
|
|
1662
|
+
const matrixComponentId = '$' + '{{ matrix.component.componentId }}';
|
|
1663
|
+
const matrixComponentPath = '$' + '{{ matrix.component.componentPath }}';
|
|
1664
|
+
const matrixArtifactName = '$' + '{{ matrix.component.artifactName }}';
|
|
1665
|
+
const matrixItems = components.map((component) => [
|
|
1666
|
+
' - componentId: ' + JSON.stringify(component.id),
|
|
1667
|
+
' componentPath: ' + JSON.stringify(component.path),
|
|
1668
|
+
' artifactName: ' + JSON.stringify(githubMatrixArtifactName(component.id)),
|
|
1669
|
+
].join('\n')).join('\n');
|
|
1670
|
+
return `name: PromptFrame Component Workspace
|
|
1671
|
+
|
|
1672
|
+
on:
|
|
1673
|
+
pull_request:
|
|
1674
|
+
push:
|
|
1675
|
+
branches: [main]
|
|
1676
|
+
tags:
|
|
1677
|
+
- 'v*'
|
|
1678
|
+
- '*@*'
|
|
1679
|
+
- 'component-*'
|
|
1680
|
+
|
|
1681
|
+
permissions:
|
|
1682
|
+
contents: read
|
|
1683
|
+
|
|
1684
|
+
jobs:
|
|
1685
|
+
check:
|
|
1686
|
+
name: PromptFrame check (${matrixComponentId})
|
|
1687
|
+
runs-on: ubuntu-latest
|
|
1688
|
+
strategy:
|
|
1689
|
+
fail-fast: false
|
|
1690
|
+
matrix:
|
|
1691
|
+
component:
|
|
1692
|
+
${matrixItems}
|
|
1693
|
+
steps:
|
|
1694
|
+
- uses: actions/checkout@v4
|
|
1695
|
+
- uses: actions/setup-node@v4
|
|
1696
|
+
with:
|
|
1697
|
+
node-version: '20'
|
|
1698
|
+
cache: npm
|
|
1699
|
+
- run: npm install
|
|
1700
|
+
- name: Validate workspace
|
|
1701
|
+
run: npx promptframe workspace validate . --json | tee promptframe-workspace-report.json
|
|
1702
|
+
- name: Validate component package
|
|
1703
|
+
env:
|
|
1704
|
+
PROMPTFRAME_API_BASE: ${apiBaseVariable}
|
|
1705
|
+
COMPONENT_ID: ${matrixComponentId}
|
|
1706
|
+
COMPONENT_PATH: ${matrixComponentPath}
|
|
1707
|
+
run: |
|
|
1708
|
+
set +e
|
|
1709
|
+
npx promptframe check . --workspace-component "$COMPONENT_ID" --json > promptframe-check.json
|
|
1710
|
+
CHECK_EXIT=$?
|
|
1711
|
+
set -e
|
|
1712
|
+
node <<'NODE'
|
|
1713
|
+
const fs = require('node:fs');
|
|
1714
|
+
const payload = JSON.parse(fs.readFileSync('promptframe-check.json', 'utf8'));
|
|
1715
|
+
const diagnostics = Array.isArray(payload.diagnostics) ? payload.diagnostics : [];
|
|
1716
|
+
for (const item of diagnostics) {
|
|
1717
|
+
const code = String(item.code || 'promptframe.diagnostic');
|
|
1718
|
+
const message = String(item.message || code).replace(/\\r?\\n/g, ' ');
|
|
1719
|
+
if (item.severity === 'error') {
|
|
1720
|
+
console.log(\`::error title=\${code}::\${message}\`);
|
|
1721
|
+
} else {
|
|
1722
|
+
console.log(\`::warning title=\${code}::\${message}\`);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
const summary = process.env.GITHUB_STEP_SUMMARY;
|
|
1726
|
+
if (summary) {
|
|
1727
|
+
fs.appendFileSync(summary, [
|
|
1728
|
+
'### PromptFrame workspace check',
|
|
1729
|
+
'',
|
|
1730
|
+
\`- Component ID: \\\`\${process.env.COMPONENT_ID || payload.source?.workspaceComponentId || 'unknown'}\\\`\`,
|
|
1731
|
+
\`- Component path: \\\`\${process.env.COMPONENT_PATH || payload.source?.componentPath || 'unknown'}\\\`\`,
|
|
1732
|
+
\`- Diagnostic: \\\`\${payload.diagnostic?.code || 'unknown'}\\\`\`,
|
|
1733
|
+
\`- Freshness: \\\`\${payload.freshness?.status || 'unknown'}\\\`\`,
|
|
1734
|
+
\`- Diagnostics: \\\`\${diagnostics.length}\\\`\`,
|
|
1735
|
+
'',
|
|
1736
|
+
].join('\\n'));
|
|
1737
|
+
}
|
|
1738
|
+
NODE
|
|
1739
|
+
exit "$CHECK_EXIT"
|
|
1740
|
+
- uses: actions/upload-artifact@v4
|
|
1741
|
+
if: always()
|
|
1742
|
+
with:
|
|
1743
|
+
name: promptframe-check-report-${matrixArtifactName}
|
|
1744
|
+
path: |
|
|
1745
|
+
promptframe-workspace-report.json
|
|
1746
|
+
promptframe-check.json
|
|
1747
|
+
|
|
1748
|
+
upload:
|
|
1749
|
+
name: PromptFrame upload (${matrixComponentId})
|
|
1750
|
+
if: github.event_name == 'push'
|
|
1751
|
+
needs: check
|
|
1752
|
+
runs-on: ubuntu-latest
|
|
1753
|
+
strategy:
|
|
1754
|
+
fail-fast: false
|
|
1755
|
+
matrix:
|
|
1756
|
+
component:
|
|
1757
|
+
${matrixItems}
|
|
1758
|
+
steps:
|
|
1759
|
+
- uses: actions/checkout@v4
|
|
1760
|
+
- uses: actions/setup-node@v4
|
|
1761
|
+
with:
|
|
1762
|
+
node-version: '20'
|
|
1763
|
+
cache: npm
|
|
1764
|
+
- run: npm install
|
|
1765
|
+
- name: Upload component to PromptFrame
|
|
1766
|
+
env:
|
|
1767
|
+
PROMPTFRAME_API_BASE: ${apiBaseVariable}
|
|
1768
|
+
PROMPTFRAME_CI_TOKEN: ${ciTokenSecret}
|
|
1769
|
+
COMPONENT_ID: ${matrixComponentId}
|
|
1770
|
+
COMPONENT_PATH: ${matrixComponentPath}
|
|
1771
|
+
run: |
|
|
1772
|
+
set -euo pipefail
|
|
1773
|
+
npx promptframe upload . --workspace-component "$COMPONENT_ID" --endpoint "$PROMPTFRAME_API_BASE" --json | tee promptframe-upload.json
|
|
1774
|
+
BUILD_ID=$(node <<'NODE'
|
|
1775
|
+
const fs = require('node:fs');
|
|
1776
|
+
const payload = JSON.parse(fs.readFileSync('promptframe-upload.json', 'utf8'));
|
|
1777
|
+
process.stdout.write(String(payload.jobId || payload.buildId || payload.build?.buildId || ''));
|
|
1778
|
+
NODE
|
|
1779
|
+
)
|
|
1780
|
+
if [ -z "$BUILD_ID" ]; then
|
|
1781
|
+
echo "PromptFrame upload response did not include a build id." >&2
|
|
1782
|
+
exit 1
|
|
1783
|
+
fi
|
|
1784
|
+
set +e
|
|
1785
|
+
npx promptframe status "$BUILD_ID" --endpoint "$PROMPTFRAME_API_BASE" --json --fail-on-build-failed | tee promptframe-status.json
|
|
1786
|
+
STATUS_EXIT=\${PIPESTATUS[0]}
|
|
1787
|
+
set -e
|
|
1788
|
+
node <<'NODE'
|
|
1789
|
+
const fs = require('node:fs');
|
|
1790
|
+
const payload = JSON.parse(fs.readFileSync('promptframe-upload.json', 'utf8'));
|
|
1791
|
+
const status = JSON.parse(fs.readFileSync('promptframe-status.json', 'utf8'));
|
|
1792
|
+
const build = status.build || {};
|
|
1793
|
+
const buildId = payload.jobId || payload.buildId || build.buildId || 'unknown';
|
|
1794
|
+
const buildStatus = String(build.status || status.status || payload.status || 'queued');
|
|
1795
|
+
const diagnostics = Array.isArray(build.diagnostics) ? build.diagnostics : [];
|
|
1796
|
+
const firstError = diagnostics.find((item) => item && item.severity === 'error') || diagnostics[0] || {};
|
|
1797
|
+
if (['failed', 'cancelled', 'canceled'].includes(buildStatus.toLowerCase())) {
|
|
1798
|
+
const message = [
|
|
1799
|
+
\`buildId=\${buildId}\`,
|
|
1800
|
+
\`status=\${buildStatus}\`,
|
|
1801
|
+
firstError.code ? \`code=\${firstError.code}\` : '',
|
|
1802
|
+
firstError.message ? \`message=\${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
1803
|
+
].filter(Boolean).join(' ');
|
|
1804
|
+
console.log(\`::error title=PromptFrame platform build failed::\${message}\`);
|
|
1805
|
+
}
|
|
1806
|
+
const summary = process.env.GITHUB_STEP_SUMMARY;
|
|
1807
|
+
if (summary) {
|
|
1808
|
+
fs.appendFileSync(summary, [
|
|
1809
|
+
'### PromptFrame workspace upload',
|
|
1810
|
+
'',
|
|
1811
|
+
\`- Component ID: \\\`\${process.env.COMPONENT_ID || payload.source?.workspaceComponentId || 'unknown'}\\\`\`,
|
|
1812
|
+
\`- Component path: \\\`\${process.env.COMPONENT_PATH || payload.source?.componentPath || 'unknown'}\\\`\`,
|
|
1813
|
+
\`- Diagnostic: \\\`\${payload.diagnostic?.code || 'unknown'}\\\`\`,
|
|
1814
|
+
\`- Build ID: \\\`\${buildId}\\\`\`,
|
|
1815
|
+
\`- Platform build status: \\\`\${buildStatus}\\\`\`,
|
|
1816
|
+
firstError.code ? \`- Top diagnostic: \\\`\${firstError.code}\\\`\` : '- Top diagnostic: none',
|
|
1817
|
+
firstError.message ? \`- Message: \${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
1818
|
+
'',
|
|
1819
|
+
].filter(Boolean).join('\\n'));
|
|
1820
|
+
}
|
|
1821
|
+
NODE
|
|
1822
|
+
exit "$STATUS_EXIT"
|
|
1823
|
+
- uses: actions/upload-artifact@v4
|
|
1824
|
+
if: always()
|
|
1825
|
+
with:
|
|
1826
|
+
name: promptframe-upload-report-${matrixArtifactName}
|
|
1827
|
+
path: |
|
|
1828
|
+
promptframe-upload.json
|
|
1829
|
+
promptframe-status.json
|
|
1830
|
+
`;
|
|
1831
|
+
}
|
|
1832
|
+
function githubMatrixArtifactName(componentId) {
|
|
1833
|
+
return componentId
|
|
1834
|
+
.replace(/^@/, '')
|
|
1835
|
+
.replace(/[^A-Za-z0-9_.-]+/g, '-')
|
|
1836
|
+
.replace(/^-+|-+$/g, '')
|
|
1837
|
+
|| 'component';
|
|
1838
|
+
}
|
|
1107
1839
|
function promptFrameGithubWorkflow() {
|
|
1108
1840
|
const apiBaseVariable = '$' + '{{ vars.PROMPTFRAME_API_BASE }}';
|
|
1109
1841
|
const ciTokenSecret = '$' + '{{ secrets.PROMPTFRAME_CI_TOKEN }}';
|
|
@@ -1200,23 +1932,43 @@ jobs:
|
|
|
1200
1932
|
echo "PromptFrame upload response did not include a build id." >&2
|
|
1201
1933
|
exit 1
|
|
1202
1934
|
fi
|
|
1203
|
-
|
|
1935
|
+
set +e
|
|
1936
|
+
npx promptframe status "$BUILD_ID" --endpoint "$PROMPTFRAME_API_BASE" --json --fail-on-build-failed | tee promptframe-status.json
|
|
1937
|
+
STATUS_EXIT=\${PIPESTATUS[0]}
|
|
1938
|
+
set -e
|
|
1204
1939
|
node <<'NODE'
|
|
1205
1940
|
const fs = require('node:fs');
|
|
1206
1941
|
const payload = JSON.parse(fs.readFileSync('promptframe-upload.json', 'utf8'));
|
|
1207
1942
|
const status = JSON.parse(fs.readFileSync('promptframe-status.json', 'utf8'));
|
|
1943
|
+
const build = status.build || {};
|
|
1944
|
+
const buildId = payload.jobId || payload.buildId || build.buildId || 'unknown';
|
|
1945
|
+
const buildStatus = String(build.status || status.status || payload.status || 'queued');
|
|
1946
|
+
const diagnostics = Array.isArray(build.diagnostics) ? build.diagnostics : [];
|
|
1947
|
+
const firstError = diagnostics.find((item) => item && item.severity === 'error') || diagnostics[0] || {};
|
|
1948
|
+
if (['failed', 'cancelled', 'canceled'].includes(buildStatus.toLowerCase())) {
|
|
1949
|
+
const message = [
|
|
1950
|
+
\`buildId=\${buildId}\`,
|
|
1951
|
+
\`status=\${buildStatus}\`,
|
|
1952
|
+
firstError.code ? \`code=\${firstError.code}\` : '',
|
|
1953
|
+
firstError.message ? \`message=\${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
1954
|
+
].filter(Boolean).join(' ');
|
|
1955
|
+
console.log(\`::error title=PromptFrame platform build failed::\${message}\`);
|
|
1956
|
+
}
|
|
1208
1957
|
const summary = process.env.GITHUB_STEP_SUMMARY;
|
|
1209
1958
|
if (summary) {
|
|
1210
1959
|
fs.appendFileSync(summary, [
|
|
1211
1960
|
'### PromptFrame upload',
|
|
1212
1961
|
'',
|
|
1213
1962
|
\`- Diagnostic: \\\`\${payload.diagnostic?.code || 'unknown'}\\\`\`,
|
|
1214
|
-
\`- Build ID: \\\`\${
|
|
1215
|
-
\`-
|
|
1963
|
+
\`- Build ID: \\\`\${buildId}\\\`\`,
|
|
1964
|
+
\`- Platform build status: \\\`\${buildStatus}\\\`\`,
|
|
1965
|
+
firstError.code ? \`- Top diagnostic: \\\`\${firstError.code}\\\`\` : '- Top diagnostic: none',
|
|
1966
|
+
firstError.message ? \`- Message: \${String(firstError.message).replace(/\\r?\\n/g, ' ').slice(0, 300)}\` : '',
|
|
1216
1967
|
'',
|
|
1217
|
-
].join('\\n'));
|
|
1968
|
+
].filter(Boolean).join('\\n'));
|
|
1218
1969
|
}
|
|
1219
1970
|
NODE
|
|
1971
|
+
exit "$STATUS_EXIT"
|
|
1220
1972
|
- uses: actions/upload-artifact@v4
|
|
1221
1973
|
if: always()
|
|
1222
1974
|
with:
|
|
@@ -1228,21 +1980,29 @@ jobs:
|
|
|
1228
1980
|
}
|
|
1229
1981
|
function resolveEndpoint(commandName, argv) {
|
|
1230
1982
|
const config = readConfig(argv);
|
|
1983
|
+
const credential = asRecord(config.credential);
|
|
1984
|
+
const projectContext = readProjectContext(argv);
|
|
1231
1985
|
const endpoint = valueAfter(argv, '--endpoint')
|
|
1232
1986
|
?? process.env.PROMPTFRAME_API_BASE
|
|
1233
1987
|
?? process.env.REMOTION_MEDIA_API_BASE
|
|
1234
|
-
?? stringValue(config.endpoint)
|
|
1988
|
+
?? stringValue(config.endpoint)
|
|
1989
|
+
?? stringValue(credential?.endpoint)
|
|
1990
|
+
?? projectContext?.endpoint;
|
|
1235
1991
|
if (!endpoint) {
|
|
1236
|
-
fail(`${commandName} requires --endpoint, PROMPTFRAME_API_BASE, REMOTION_MEDIA_API_BASE,
|
|
1992
|
+
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);
|
|
1237
1993
|
}
|
|
1238
1994
|
return normalizeEndpoint(endpoint);
|
|
1239
1995
|
}
|
|
1240
1996
|
function resolveOptionalEndpoint(argv) {
|
|
1241
1997
|
const config = readConfig(argv);
|
|
1998
|
+
const credential = asRecord(config.credential);
|
|
1999
|
+
const projectContext = readProjectContext(argv);
|
|
1242
2000
|
const endpoint = valueAfter(argv, '--endpoint')
|
|
1243
2001
|
?? stringValue(process.env.PROMPTFRAME_API_BASE)
|
|
1244
2002
|
?? stringValue(process.env.REMOTION_MEDIA_API_BASE)
|
|
1245
|
-
?? stringValue(config.endpoint)
|
|
2003
|
+
?? stringValue(config.endpoint)
|
|
2004
|
+
?? stringValue(credential?.endpoint)
|
|
2005
|
+
?? projectContext?.endpoint;
|
|
1246
2006
|
return endpoint ? normalizeEndpoint(endpoint) : undefined;
|
|
1247
2007
|
}
|
|
1248
2008
|
function buildContextHeaders(argv) {
|
|
@@ -1271,7 +2031,7 @@ function buildRemoteHeaders(endpoint, argv, options = {}) {
|
|
|
1271
2031
|
'x-auth-permissions',
|
|
1272
2032
|
].includes(name));
|
|
1273
2033
|
if (isFormalEndpoint(endpoint) && hasDevHeaders) {
|
|
1274
|
-
fail(
|
|
2034
|
+
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');
|
|
1275
2035
|
}
|
|
1276
2036
|
if (isFormalEndpoint(endpoint) && options.requireAuth !== false) {
|
|
1277
2037
|
fail('No PromptFrame CLI credential found for this endpoint. Run promptframe login --endpoint <url> or provide PROMPTFRAME_CI_TOKEN.', 'cli.auth.login_required', 2);
|
|
@@ -1411,6 +2171,110 @@ function writeConfig(argv, config) {
|
|
|
1411
2171
|
function resolveConfigPath(argv) {
|
|
1412
2172
|
return resolve(valueAfter(argv, '--config') ?? process.env.PROMPTFRAME_CONFIG ?? join(os.homedir(), '.promptframe', 'component-authoring.json'));
|
|
1413
2173
|
}
|
|
2174
|
+
function readProjectContext(argv) {
|
|
2175
|
+
const explicitPath = valueAfter(argv, '--project-context');
|
|
2176
|
+
const contextPath = explicitPath
|
|
2177
|
+
? resolve(explicitPath)
|
|
2178
|
+
: findProjectContextPath(process.cwd());
|
|
2179
|
+
if (!contextPath || !existsSync(contextPath))
|
|
2180
|
+
return undefined;
|
|
2181
|
+
let raw;
|
|
2182
|
+
try {
|
|
2183
|
+
raw = JSON.parse(readFileSync(contextPath, 'utf8'));
|
|
2184
|
+
}
|
|
2185
|
+
catch {
|
|
2186
|
+
fail(`PromptFrame project context is not valid JSON: ${contextPath}`, 'project_context.invalid_json', 2);
|
|
2187
|
+
}
|
|
2188
|
+
return parseProjectContext(raw, contextPath);
|
|
2189
|
+
}
|
|
2190
|
+
function findProjectContextPath(startDir) {
|
|
2191
|
+
let current = resolve(startDir);
|
|
2192
|
+
for (;;) {
|
|
2193
|
+
const candidate = join(current, PROMPTFRAME_PROJECT_CONTEXT_FILE_NAME);
|
|
2194
|
+
if (existsSync(candidate))
|
|
2195
|
+
return candidate;
|
|
2196
|
+
const parent = dirname(current);
|
|
2197
|
+
if (parent === current)
|
|
2198
|
+
return undefined;
|
|
2199
|
+
current = parent;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function parseProjectContext(value, sourcePath) {
|
|
2203
|
+
const record = asRecord(value);
|
|
2204
|
+
if (!record) {
|
|
2205
|
+
fail(`PromptFrame project context must be a JSON object: ${sourcePath}`, 'project_context.invalid', 2);
|
|
2206
|
+
}
|
|
2207
|
+
assertProjectContextSecretFree(record, sourcePath);
|
|
2208
|
+
const schemaVersion = stringValue(record.schemaVersion);
|
|
2209
|
+
if (schemaVersion && schemaVersion !== PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION) {
|
|
2210
|
+
fail(`Unsupported PromptFrame project context schemaVersion ${schemaVersion}. Expected ${PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION}.`, 'project_context.schema_unsupported', 2);
|
|
2211
|
+
}
|
|
2212
|
+
return {
|
|
2213
|
+
schemaVersion: PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION,
|
|
2214
|
+
...(stringValue(record.endpoint) ? { endpoint: normalizeEndpoint(stringValue(record.endpoint)) } : {}),
|
|
2215
|
+
...(stringValue(record.tenantId) ? { tenantId: stringValue(record.tenantId) } : {}),
|
|
2216
|
+
...(stringValue(record.projectId) ? { projectId: stringValue(record.projectId) } : {}),
|
|
2217
|
+
...(stringValue(record.projectNamespace) ? { projectNamespace: stringValue(record.projectNamespace) } : {}),
|
|
2218
|
+
...(stringValue(record.defaultUploadTarget) ? { defaultUploadTarget: stringValue(record.defaultUploadTarget) } : {}),
|
|
2219
|
+
...(stringValue(record.workspaceConfig) ? { workspaceConfig: stringValue(record.workspaceConfig) } : {}),
|
|
2220
|
+
};
|
|
2221
|
+
}
|
|
2222
|
+
function buildProjectContext(input) {
|
|
2223
|
+
const context = {
|
|
2224
|
+
schemaVersion: PROMPTFRAME_PROJECT_CONTEXT_SCHEMA_VERSION,
|
|
2225
|
+
endpoint: normalizeEndpoint(input.endpoint),
|
|
2226
|
+
...(input.tenantId ? { tenantId: input.tenantId } : {}),
|
|
2227
|
+
projectId: input.projectId,
|
|
2228
|
+
...(input.projectNamespace ? { projectNamespace: normalizeProjectNamespace(input.projectNamespace) } : {}),
|
|
2229
|
+
...(input.defaultUploadTarget ? { defaultUploadTarget: input.defaultUploadTarget } : {}),
|
|
2230
|
+
...(input.workspaceConfig ? { workspaceConfig: input.workspaceConfig } : {}),
|
|
2231
|
+
};
|
|
2232
|
+
assertProjectContextSecretFree(context, PROMPTFRAME_PROJECT_CONTEXT_FILE_NAME);
|
|
2233
|
+
return context;
|
|
2234
|
+
}
|
|
2235
|
+
function assertProjectContextSecretFree(value, sourcePath) {
|
|
2236
|
+
if (!value || typeof value !== 'object')
|
|
2237
|
+
return;
|
|
2238
|
+
if (Array.isArray(value)) {
|
|
2239
|
+
for (const item of value)
|
|
2240
|
+
assertProjectContextSecretFree(item, sourcePath);
|
|
2241
|
+
return;
|
|
2242
|
+
}
|
|
2243
|
+
for (const [key, child] of Object.entries(value)) {
|
|
2244
|
+
const normalizedKey = key.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
2245
|
+
if (PROJECT_CONTEXT_FORBIDDEN_KEY_NAMES.has(normalizedKey)) {
|
|
2246
|
+
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);
|
|
2247
|
+
}
|
|
2248
|
+
assertProjectContextSecretFree(child, sourcePath);
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
2251
|
+
function assertComponentId(value) {
|
|
2252
|
+
if (!/^@[a-z0-9][a-z0-9-]{1,62}\/[a-z0-9][a-z0-9-]{1,62}$/.test(value)) {
|
|
2253
|
+
fail(`Invalid component id "${value}". Expected @project-namespace/component-slug.`, 'component.id_invalid', 2);
|
|
2254
|
+
}
|
|
2255
|
+
return value;
|
|
2256
|
+
}
|
|
2257
|
+
function normalizeProjectNamespace(value) {
|
|
2258
|
+
const normalized = value
|
|
2259
|
+
.trim()
|
|
2260
|
+
.toLowerCase()
|
|
2261
|
+
.replace(/^@/, '')
|
|
2262
|
+
.replace(/[^a-z0-9-]+/g, '-')
|
|
2263
|
+
.replace(/^-+|-+$/g, '')
|
|
2264
|
+
.slice(0, 63);
|
|
2265
|
+
if (!/^[a-z0-9][a-z0-9-]{1,62}$/.test(normalized)) {
|
|
2266
|
+
fail(`Invalid project namespace "${value}".`, 'project_context.namespace_invalid', 2);
|
|
2267
|
+
}
|
|
2268
|
+
return normalized;
|
|
2269
|
+
}
|
|
2270
|
+
function displayNameFromComponentId(componentId) {
|
|
2271
|
+
const slug = componentId.split('/').at(-1) ?? componentId;
|
|
2272
|
+
return slug
|
|
2273
|
+
.split('-')
|
|
2274
|
+
.filter(Boolean)
|
|
2275
|
+
.map((part) => `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`)
|
|
2276
|
+
.join(' ');
|
|
2277
|
+
}
|
|
1414
2278
|
async function fetchJson(url, init, code) {
|
|
1415
2279
|
const response = await fetch(url, init);
|
|
1416
2280
|
const payload = await response.json().catch(() => ({}));
|
|
@@ -2095,6 +2959,26 @@ function printBuildSummary(payload) {
|
|
|
2095
2959
|
console.log(`${stringValue(diagnosticItem?.severity)?.toUpperCase() ?? 'INFO'} ${stringValue(diagnosticItem?.code) ?? 'unknown'}: ${stringValue(diagnosticItem?.message) ?? ''}`);
|
|
2096
2960
|
}
|
|
2097
2961
|
}
|
|
2962
|
+
function failIfBuildFailed(payload) {
|
|
2963
|
+
const status = getBuildStatus(payload);
|
|
2964
|
+
if (!status || !['failed', 'cancelled', 'canceled'].includes(status.toLowerCase()))
|
|
2965
|
+
return;
|
|
2966
|
+
const buildId = getBuildId(payload) ?? 'unknown';
|
|
2967
|
+
const diagnosticItem = firstBuildDiagnostic(payload);
|
|
2968
|
+
const diagnosticText = diagnosticItem
|
|
2969
|
+
? `${stringValue(diagnosticItem.code) ?? 'unknown'}: ${stringValue(diagnosticItem.message) ?? ''}`.trim()
|
|
2970
|
+
: 'no diagnostic';
|
|
2971
|
+
fail(`PromptFrame platform build failed: buildId=${buildId} status=${status} diagnostic=${diagnosticText}`, 'status.build.failed', 1);
|
|
2972
|
+
}
|
|
2973
|
+
function getBuildStatus(payload) {
|
|
2974
|
+
return stringValue(asRecord(payload.build)?.status) ?? stringValue(payload.status);
|
|
2975
|
+
}
|
|
2976
|
+
function firstBuildDiagnostic(payload) {
|
|
2977
|
+
const diagnostics = arrayValue(asRecord(payload.build)?.diagnostics)
|
|
2978
|
+
.map((item) => asRecord(item))
|
|
2979
|
+
.filter((item) => Boolean(item));
|
|
2980
|
+
return diagnostics.find((item) => item.severity === 'error') ?? diagnostics[0];
|
|
2981
|
+
}
|
|
2098
2982
|
function printStatusUrl(endpoint, payload) {
|
|
2099
2983
|
const statusUrl = stringValue(payload.statusUrl) ?? stringValue(asRecord(payload.build)?.statusUrl);
|
|
2100
2984
|
if (!statusUrl)
|
|
@@ -2143,6 +3027,7 @@ Commands:
|
|
|
2143
3027
|
--target <target> marketplace_authoring or project_private_generation
|
|
2144
3028
|
--target marketplace --strict Alias for strict external marketplace authoring
|
|
2145
3029
|
status <buildId> Fetch component build status
|
|
3030
|
+
--fail-on-build-failed Exit nonzero when platform build status is failed/cancelled
|
|
2146
3031
|
reindex <buildId> Rebuild component search/evidence indexes
|
|
2147
3032
|
probe <buildId> --level <level> Rerun component layout/security probe
|
|
2148
3033
|
login --endpoint <url> Start browser login code flow and store a CLI token
|
|
@@ -2150,13 +3035,15 @@ Commands:
|
|
|
2150
3035
|
whoami Show the current PromptFrame CLI identity
|
|
2151
3036
|
logout Revoke the current CLI token and clear local config
|
|
2152
3037
|
discovery Fetch platform endpoint and self-service capabilities
|
|
3038
|
+
init [dir] Write secret-free .promptframerc project context
|
|
2153
3039
|
project list|current List accessible projects or show the active project
|
|
3040
|
+
component list|create List or declare Project-scoped components
|
|
2154
3041
|
ci-token create|list|revoke Manage self-service CI tokens for the current project
|
|
2155
3042
|
setup-ci [dir] --provider github Write a GitHub Actions workflow skeleton
|
|
2156
3043
|
configure --endpoint <url> Write local CLI endpoint/context config
|
|
2157
3044
|
|
|
2158
3045
|
Endpoint resolution:
|
|
2159
|
-
--endpoint, PROMPTFRAME_API_BASE, REMOTION_MEDIA_API_BASE,
|
|
3046
|
+
--endpoint, PROMPTFRAME_API_BASE, REMOTION_MEDIA_API_BASE, local config, then .promptframerc.
|
|
2160
3047
|
The public CLI embeds no production/private endpoint defaults.
|
|
2161
3048
|
Auth context:
|
|
2162
3049
|
promptframe login, PROMPTFRAME_CI_TOKEN, or PROMPTFRAME_CLI_TOKEN for formal endpoints.
|