@tenderprompt/cli 0.1.27 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +192 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -434,8 +434,8 @@ Examples:
|
|
|
434
434
|
printf "$SHOPIFY_APP_AUTOMATION_TOKEN" | tender connectors shopify app-automation-token set conn_123 --token-stdin --expires-at 2026-08-22 --profile account --json
|
|
435
435
|
tender connectors shopify verify conn_123 --profile account --json
|
|
436
436
|
tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --tender-app-url https://example.tenderprompt.app/client.js --tender-element tender-widget --profile account --json
|
|
437
|
-
tender connectors shopify source validate --dir ./shopify-app --execute --json
|
|
438
|
-
SHOPIFY_APP_AUTOMATION_TOKEN=shpat_... tender connectors shopify source deploy --dir ./shopify-app --confirm deploy --json
|
|
437
|
+
tender connectors shopify source validate conn_123 --dir ./shopify-app --execute --json
|
|
438
|
+
SHOPIFY_APP_AUTOMATION_TOKEN=shpat_... tender connectors shopify source deploy conn_123 --dir ./shopify-app --confirm deploy --json
|
|
439
439
|
tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json`;
|
|
440
440
|
}
|
|
441
441
|
function connectorsShopifyCreateHelp() {
|
|
@@ -1476,6 +1476,47 @@ function tenderCliCapabilities() {
|
|
|
1476
1476
|
persistence: 'session_only',
|
|
1477
1477
|
writesToCheckout: false,
|
|
1478
1478
|
},
|
|
1479
|
+
connectors: {
|
|
1480
|
+
mode: 'tenant_owned_platform_bindings',
|
|
1481
|
+
listCommand: 'tender connectors list --profile account --json',
|
|
1482
|
+
bindCommand: 'tender connectors bind <binding-name> <connector-id> --instance <instance-id> --json',
|
|
1483
|
+
shopify: {
|
|
1484
|
+
playbookId: 'shopify-connector-custom-app',
|
|
1485
|
+
playbookCommand: 'tender playbooks get shopify-connector-custom-app --json',
|
|
1486
|
+
customerMetadataPlaybookCommand: 'tender playbooks get shopify-connector-customer-metadata-widget --json',
|
|
1487
|
+
whatItIs: 'A merchant-owned Shopify custom app exposed to Tender apps through an explicit platform connector binding.',
|
|
1488
|
+
whenToUse: [
|
|
1489
|
+
'Shopify Admin GraphQL reads or writes',
|
|
1490
|
+
'customer metafields or customer-linked metaobjects',
|
|
1491
|
+
'app-owned custom data definitions',
|
|
1492
|
+
'Theme App Extension app blocks',
|
|
1493
|
+
'privileged background Shopify work that browser code cannot safely perform',
|
|
1494
|
+
],
|
|
1495
|
+
whenNotToUse: [
|
|
1496
|
+
'pure frontend widgets',
|
|
1497
|
+
'Tender analytics only',
|
|
1498
|
+
'public Shopify data already present on the page',
|
|
1499
|
+
'client-side event publishing without Admin API writes',
|
|
1500
|
+
],
|
|
1501
|
+
binding: {
|
|
1502
|
+
name: 'SHOPIFY_CONNECTOR',
|
|
1503
|
+
source: 'platform:shopify_connector',
|
|
1504
|
+
interface: 'ShopifyConnector',
|
|
1505
|
+
callShape: 'await this.env.SHOPIFY_CONNECTOR.invoke("shopify.graphql", { query, variables })',
|
|
1506
|
+
},
|
|
1507
|
+
sourceProject: {
|
|
1508
|
+
durableIdentity: 'connector.sourceProject.sourceArtifactId',
|
|
1509
|
+
targetPackId: 'shopify-connector-source',
|
|
1510
|
+
visibility: 'internal_source_artifact_not_customer_facing_app',
|
|
1511
|
+
},
|
|
1512
|
+
guardrails: [
|
|
1513
|
+
'Use the account profile because connectors are account-level resources.',
|
|
1514
|
+
'Use minimum Shopify scopes for the requested job.',
|
|
1515
|
+
'Never commit client secrets, Admin API tokens, app automation tokens, Tender tokens, or customer data exports.',
|
|
1516
|
+
'Do not create bindings/<id>/ files for Shopify connectors; use the platform connector source in app.json.',
|
|
1517
|
+
],
|
|
1518
|
+
},
|
|
1519
|
+
},
|
|
1479
1520
|
analyticsAuthoring: {
|
|
1480
1521
|
command: ANALYTICS_AUTHORING_COMMAND,
|
|
1481
1522
|
playbookId: ANALYTICS_AUTHORING_PLAYBOOK_ID,
|
|
@@ -1502,6 +1543,8 @@ function tenderCliCapabilities() {
|
|
|
1502
1543
|
'tender app db --help',
|
|
1503
1544
|
'tender app bindings outbound --help',
|
|
1504
1545
|
'tender app outbound-auth --help',
|
|
1546
|
+
'tender connectors --help',
|
|
1547
|
+
'tender connectors shopify source init --help',
|
|
1505
1548
|
],
|
|
1506
1549
|
},
|
|
1507
1550
|
workflows: [
|
|
@@ -1527,6 +1570,23 @@ function tenderCliCapabilities() {
|
|
|
1527
1570
|
'Do not write fetched playbook contents into the checkout or install them as skills.',
|
|
1528
1571
|
],
|
|
1529
1572
|
},
|
|
1573
|
+
{
|
|
1574
|
+
name: 'shopify_connector_discovery',
|
|
1575
|
+
when: 'When a Tender App needs Shopify Admin API access, customer metadata, app blocks, app-owned custom data, or a merchant-owned Shopify custom app.',
|
|
1576
|
+
commands: [
|
|
1577
|
+
'tender capabilities --json',
|
|
1578
|
+
'tender playbooks list --json',
|
|
1579
|
+
'tender playbooks get shopify-connector-custom-app --json',
|
|
1580
|
+
'tender connectors list --profile account --json',
|
|
1581
|
+
'tender connectors bind SHOPIFY_CONNECTOR <connector-id> --instance default --json',
|
|
1582
|
+
],
|
|
1583
|
+
notes: [
|
|
1584
|
+
'Connectors are tenant-owned platform bindings, not generated binding workers.',
|
|
1585
|
+
'The merchant owns the Shopify custom app; Tender stores connector credentials and app source metadata.',
|
|
1586
|
+
'Use connector.sourceProject.sourceArtifactId as the durable Shopify app source identity.',
|
|
1587
|
+
'Fetch shopify-connector-customer-metadata-widget when the requested job involves customer metafields, metaobjects, or profile writes.',
|
|
1588
|
+
],
|
|
1589
|
+
},
|
|
1530
1590
|
{
|
|
1531
1591
|
name: 'create_new_app_with_preview',
|
|
1532
1592
|
when: 'When the user wants a new Tender App that is immediately visible.',
|
|
@@ -11116,7 +11176,9 @@ function defaultDogProfileScopes(connectorScopes, override) {
|
|
|
11116
11176
|
}
|
|
11117
11177
|
function tenderWidgetAssetJs() {
|
|
11118
11178
|
return `(function () {
|
|
11119
|
-
const loader = (window.TenderPromptBlockLoader ||= {
|
|
11179
|
+
const loader = (window.TenderPromptBlockLoader ||= {});
|
|
11180
|
+
loader.scripts ||= new Map();
|
|
11181
|
+
loader.stylesheets ||= new Map();
|
|
11120
11182
|
|
|
11121
11183
|
function loadScript(src) {
|
|
11122
11184
|
if (!src) return Promise.reject(new Error("Missing Tender runtime URL"));
|
|
@@ -11142,6 +11204,96 @@ function tenderWidgetAssetJs() {
|
|
|
11142
11204
|
return promise;
|
|
11143
11205
|
}
|
|
11144
11206
|
|
|
11207
|
+
function preloadStylesheet(src) {
|
|
11208
|
+
if (!src) return Promise.resolve();
|
|
11209
|
+
const existing = loader.stylesheets.get(src);
|
|
11210
|
+
if (existing) return existing.promise;
|
|
11211
|
+
|
|
11212
|
+
const promise = new Promise((resolve) => {
|
|
11213
|
+
const link = document.createElement("link");
|
|
11214
|
+
link.rel = "preload";
|
|
11215
|
+
link.as = "style";
|
|
11216
|
+
link.href = src;
|
|
11217
|
+
link.onload = resolve;
|
|
11218
|
+
link.onerror = resolve;
|
|
11219
|
+
document.head.appendChild(link);
|
|
11220
|
+
});
|
|
11221
|
+
|
|
11222
|
+
loader.stylesheets.set(src, { status: "loading", promise });
|
|
11223
|
+
promise.then(() => loader.stylesheets.set(src, { status: "loaded", promise }));
|
|
11224
|
+
return promise;
|
|
11225
|
+
}
|
|
11226
|
+
|
|
11227
|
+
function afterFrame(callback) {
|
|
11228
|
+
requestAnimationFrame(() => requestAnimationFrame(callback));
|
|
11229
|
+
}
|
|
11230
|
+
|
|
11231
|
+
function stylesheetLoaded(href) {
|
|
11232
|
+
return performance.getEntriesByName(href).some((entry) => entry.responseEnd > 0);
|
|
11233
|
+
}
|
|
11234
|
+
|
|
11235
|
+
function waitForWidgetPaintReady(widget, stylesheetUrl) {
|
|
11236
|
+
const stylesheetHref = stylesheetUrl ? new URL(stylesheetUrl, document.baseURI).href : null;
|
|
11237
|
+
|
|
11238
|
+
return new Promise((resolve) => {
|
|
11239
|
+
let settled = false;
|
|
11240
|
+
let noShadowTimer = null;
|
|
11241
|
+
let rootWithoutStylesheetTimer = null;
|
|
11242
|
+
const finish = () => {
|
|
11243
|
+
if (settled) return;
|
|
11244
|
+
settled = true;
|
|
11245
|
+
clearInterval(interval);
|
|
11246
|
+
clearTimeout(timeout);
|
|
11247
|
+
clearTimeout(noShadowTimer);
|
|
11248
|
+
clearTimeout(rootWithoutStylesheetTimer);
|
|
11249
|
+
afterFrame(resolve);
|
|
11250
|
+
};
|
|
11251
|
+
|
|
11252
|
+
const check = () => {
|
|
11253
|
+
const root = widget.shadowRoot;
|
|
11254
|
+
if (!root) {
|
|
11255
|
+
noShadowTimer ||= setTimeout(finish, 350);
|
|
11256
|
+
return;
|
|
11257
|
+
}
|
|
11258
|
+
|
|
11259
|
+
clearTimeout(noShadowTimer);
|
|
11260
|
+
const links = Array.from(root.querySelectorAll('link[rel="stylesheet"]'));
|
|
11261
|
+
const stylesheet = stylesheetHref
|
|
11262
|
+
? links.find((link) => link.href === stylesheetHref)
|
|
11263
|
+
: links[0];
|
|
11264
|
+
|
|
11265
|
+
if (!stylesheet) {
|
|
11266
|
+
rootWithoutStylesheetTimer ||= setTimeout(finish, 500);
|
|
11267
|
+
return;
|
|
11268
|
+
}
|
|
11269
|
+
|
|
11270
|
+
clearTimeout(rootWithoutStylesheetTimer);
|
|
11271
|
+
if (stylesheet.sheet || stylesheetLoaded(stylesheet.href)) {
|
|
11272
|
+
finish();
|
|
11273
|
+
return;
|
|
11274
|
+
}
|
|
11275
|
+
|
|
11276
|
+
stylesheet.addEventListener("load", finish, { once: true });
|
|
11277
|
+
stylesheet.addEventListener("error", finish, { once: true });
|
|
11278
|
+
};
|
|
11279
|
+
|
|
11280
|
+
const interval = setInterval(check, 25);
|
|
11281
|
+
const timeout = setTimeout(finish, 2200);
|
|
11282
|
+
check();
|
|
11283
|
+
});
|
|
11284
|
+
}
|
|
11285
|
+
|
|
11286
|
+
function revealWidget(mount, widget) {
|
|
11287
|
+
const placeholder = mount.querySelector("[data-tender-placeholder]");
|
|
11288
|
+
widget.style.visibility = "";
|
|
11289
|
+
widget.style.position = "";
|
|
11290
|
+
widget.style.inset = "";
|
|
11291
|
+
widget.style.pointerEvents = "";
|
|
11292
|
+
placeholder?.remove();
|
|
11293
|
+
mount.dataset.tenderReady = "true";
|
|
11294
|
+
mount.setAttribute("aria-busy", "false");
|
|
11295
|
+
}
|
|
11296
|
+
|
|
11145
11297
|
class TenderPromptWidgetBlock extends HTMLElement {
|
|
11146
11298
|
connectedCallback() {
|
|
11147
11299
|
this.mount();
|
|
@@ -11152,7 +11304,13 @@ function tenderWidgetAssetJs() {
|
|
|
11152
11304
|
const elementTag = this.dataset.tenderElementTag;
|
|
11153
11305
|
if (!mount || !elementTag || mount.dataset.tenderMounted === "true") return;
|
|
11154
11306
|
|
|
11307
|
+
mount.setAttribute("aria-busy", "true");
|
|
11308
|
+
preloadStylesheet(this.dataset.stylesheetUrl);
|
|
11155
11309
|
await loadScript(this.dataset.tenderRuntimeSrc);
|
|
11310
|
+
await Promise.race([
|
|
11311
|
+
customElements.whenDefined(elementTag),
|
|
11312
|
+
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
11313
|
+
]);
|
|
11156
11314
|
|
|
11157
11315
|
const widget = document.createElement(elementTag);
|
|
11158
11316
|
for (const [key, value] of Object.entries(this.dataset)) {
|
|
@@ -11160,8 +11318,14 @@ function tenderWidgetAssetJs() {
|
|
|
11160
11318
|
widget.dataset[key] = value;
|
|
11161
11319
|
}
|
|
11162
11320
|
|
|
11163
|
-
|
|
11321
|
+
widget.style.visibility = "hidden";
|
|
11322
|
+
widget.style.position = "absolute";
|
|
11323
|
+
widget.style.inset = "0";
|
|
11324
|
+
widget.style.pointerEvents = "none";
|
|
11325
|
+
mount.appendChild(widget);
|
|
11164
11326
|
mount.dataset.tenderMounted = "true";
|
|
11327
|
+
await waitForWidgetPaintReady(widget, this.dataset.stylesheetUrl);
|
|
11328
|
+
revealWidget(mount, widget);
|
|
11165
11329
|
}
|
|
11166
11330
|
}
|
|
11167
11331
|
|
|
@@ -11180,6 +11344,7 @@ function buildShopifySourceFiles(input) {
|
|
|
11180
11344
|
const scopes = defaultDogProfileScopes(input.connector.scopes, input.scopes);
|
|
11181
11345
|
const artifactId = input.artifactId ?? 'artifact_replace_me';
|
|
11182
11346
|
const publicId = input.publicId ?? 'tender-widget';
|
|
11347
|
+
const stylesheetUrl = new URL('./styles.css', input.tenderAppUrl).href;
|
|
11183
11348
|
const blockName = input.blockName.trim().slice(0, 120) || 'Tender Prompt Widget';
|
|
11184
11349
|
const connectorManifest = {
|
|
11185
11350
|
version: 1,
|
|
@@ -11196,6 +11361,7 @@ function buildShopifySourceFiles(input) {
|
|
|
11196
11361
|
publicId,
|
|
11197
11362
|
elementTag: tenderElement,
|
|
11198
11363
|
runtimeUrl: input.tenderAppUrl,
|
|
11364
|
+
stylesheetUrl,
|
|
11199
11365
|
},
|
|
11200
11366
|
secrets: {
|
|
11201
11367
|
clientSecret: 'stored in Tender connector credentials, not in this source tree',
|
|
@@ -11258,8 +11424,13 @@ access.admin = "merchant_read_write"
|
|
|
11258
11424
|
|
|
11259
11425
|
{% liquid
|
|
11260
11426
|
assign artifact_id = block.settings.artifact_id | default: ${tomlString(artifactId)}
|
|
11261
|
-
assign runtime_src = block.settings.runtime_src | default: ${tomlString(input.tenderAppUrl)}
|
|
11262
11427
|
assign widget_version = block.settings.widget_version | default: 'latest'
|
|
11428
|
+
|
|
11429
|
+
case widget_version
|
|
11430
|
+
else
|
|
11431
|
+
assign runtime_src = ${tomlString(input.tenderAppUrl)}
|
|
11432
|
+
assign stylesheet_src = ${tomlString(stylesheetUrl)}
|
|
11433
|
+
endcase
|
|
11263
11434
|
%}
|
|
11264
11435
|
|
|
11265
11436
|
<tender-prompt-widget-block
|
|
@@ -11269,6 +11440,7 @@ access.admin = "merchant_read_write"
|
|
|
11269
11440
|
data-tender-public-id="{{ block.settings.public_id | escape }}"
|
|
11270
11441
|
data-tender-version="{{ widget_version | escape }}"
|
|
11271
11442
|
data-tender-runtime-src="{{ runtime_src | escape }}"
|
|
11443
|
+
data-stylesheet-url="{{ stylesheet_src | escape }}"
|
|
11272
11444
|
data-tender-element-tag="{{ block.settings.element_tag | escape }}"
|
|
11273
11445
|
data-tender-mode="shopify_app_block"
|
|
11274
11446
|
data-surface="shopify_theme_app_extension"
|
|
@@ -11285,8 +11457,21 @@ access.admin = "merchant_read_write"
|
|
|
11285
11457
|
data-tender-product-handle="{{ product.handle | escape }}"
|
|
11286
11458
|
{% endif %}
|
|
11287
11459
|
>
|
|
11288
|
-
<div data-tender-mount>
|
|
11289
|
-
<
|
|
11460
|
+
<div data-tender-mount style="position: relative;">
|
|
11461
|
+
<div
|
|
11462
|
+
data-tender-placeholder
|
|
11463
|
+
style="box-sizing: border-box; display: grid; gap: 14px; min-height: 260px; padding: 28px; border: 1px solid rgba(0,0,0,.12); border-radius: 8px; background: rgba(255,255,255,.78); color: currentColor;"
|
|
11464
|
+
>
|
|
11465
|
+
<div style="display: grid; gap: 8px;">
|
|
11466
|
+
<p style="margin: 0; font: inherit; font-weight: 700;">{{ block.settings.heading }}</p>
|
|
11467
|
+
<p style="margin: 0; max-width: 42rem; opacity: .72;">{{ block.settings.intro }}</p>
|
|
11468
|
+
</div>
|
|
11469
|
+
<div style="display: grid; gap: 10px;" aria-hidden="true">
|
|
11470
|
+
<span style="display: block; width: 52%; height: 12px; border-radius: 999px; background: rgba(0,0,0,.10);"></span>
|
|
11471
|
+
<span style="display: block; width: 100%; height: 44px; border-radius: 8px; background: rgba(0,0,0,.07);"></span>
|
|
11472
|
+
<span style="display: block; width: 88%; height: 44px; border-radius: 8px; background: rgba(0,0,0,.07);"></span>
|
|
11473
|
+
</div>
|
|
11474
|
+
</div>
|
|
11290
11475
|
</div>
|
|
11291
11476
|
</tender-prompt-widget-block>
|
|
11292
11477
|
|
|
@@ -11296,12 +11481,6 @@ access.admin = "merchant_read_write"
|
|
|
11296
11481
|
"target": "section",
|
|
11297
11482
|
"javascript": "tender-widget.js",
|
|
11298
11483
|
"settings": [
|
|
11299
|
-
{
|
|
11300
|
-
"type": "text",
|
|
11301
|
-
"id": "runtime_src",
|
|
11302
|
-
"label": "Tender runtime URL",
|
|
11303
|
-
"default": ${JSON.stringify(input.tenderAppUrl)}
|
|
11304
|
-
},
|
|
11305
11484
|
{
|
|
11306
11485
|
"type": "text",
|
|
11307
11486
|
"id": "element_tag",
|