@tenderprompt/cli 0.1.28 → 0.1.29
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.d.ts +5 -3
- package/dist/index.js +188 -448
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -9,9 +9,11 @@ type TenderCliRuntime = {
|
|
|
9
9
|
sleeper?: (milliseconds: number) => Promise<void>;
|
|
10
10
|
now?: () => number;
|
|
11
11
|
stdin?: string;
|
|
12
|
-
commandRunner?: (command: string, args: string[], options:
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
commandRunner?: (command: string, args: string[], options: TenderCommandOptions) => Promise<TenderCommandResult>;
|
|
13
|
+
};
|
|
14
|
+
type TenderCommandOptions = {
|
|
15
|
+
cwd: string;
|
|
16
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
15
17
|
};
|
|
16
18
|
type TenderCommandResult = {
|
|
17
19
|
exitCode: number;
|
package/dist/index.js
CHANGED
|
@@ -95,6 +95,14 @@ class TenderCliUsageError extends Error {
|
|
|
95
95
|
this.name = 'TenderCliUsageError';
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
+
class TenderCliGuidedUsageError extends TenderCliUsageError {
|
|
99
|
+
nextAction;
|
|
100
|
+
constructor(message, nextAction) {
|
|
101
|
+
super(message);
|
|
102
|
+
this.name = 'TenderCliGuidedUsageError';
|
|
103
|
+
this.nextAction = nextAction;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
98
106
|
class TenderCliAnalyticsApiError extends Error {
|
|
99
107
|
reasonCode;
|
|
100
108
|
scope;
|
|
@@ -121,6 +129,18 @@ class TenderCliDbApiError extends Error {
|
|
|
121
129
|
this.nextAction = nextAction;
|
|
122
130
|
}
|
|
123
131
|
}
|
|
132
|
+
class TenderCliConnectorsApiError extends Error {
|
|
133
|
+
reasonCode;
|
|
134
|
+
status;
|
|
135
|
+
payload;
|
|
136
|
+
constructor(message, reasonCode, status, payload = null) {
|
|
137
|
+
super(message);
|
|
138
|
+
this.name = 'TenderCliConnectorsApiError';
|
|
139
|
+
this.reasonCode = reasonCode;
|
|
140
|
+
this.status = status;
|
|
141
|
+
this.payload = payload;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
124
144
|
const DEFAULT_PROFILE_NAME = 'default';
|
|
125
145
|
const DEFAULT_BASE_URL = 'https://app.tenderprompt.com';
|
|
126
146
|
const ARTIFACT_SOURCE_EXPORT_MAX_FILES = 1000;
|
|
@@ -433,9 +453,9 @@ Examples:
|
|
|
433
453
|
printf "$SHOPIFY_CLIENT_SECRET" | tender connectors shopify credentials set conn_123 --shop demo.myshopify.com --client-id abc --client-secret-stdin --profile account --json
|
|
434
454
|
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
455
|
tender connectors shopify verify conn_123 --profile account --json
|
|
436
|
-
tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --
|
|
456
|
+
tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --profile account --json
|
|
437
457
|
tender connectors shopify source validate conn_123 --dir ./shopify-app --execute --json
|
|
438
|
-
|
|
458
|
+
tender connectors shopify source deploy conn_123 --dir ./shopify-app --confirm deploy --profile account --json
|
|
439
459
|
tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json`;
|
|
440
460
|
}
|
|
441
461
|
function connectorsShopifyCreateHelp() {
|
|
@@ -493,16 +513,19 @@ Examples:
|
|
|
493
513
|
tender connectors shopify verify conn_123 --profile account --json`;
|
|
494
514
|
}
|
|
495
515
|
function connectorsShopifySourceInitHelp() {
|
|
496
|
-
return `Usage: tender connectors shopify source init <connector-id> --dir <path> --client-id <shopify-client-id>
|
|
516
|
+
return `Usage: tender connectors shopify source init <connector-id> --dir <path> --client-id <shopify-client-id> [--source-name <name>] [--scopes <csv>] [--force] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
497
517
|
|
|
498
518
|
Creates or reuses an internal Tender source artifact for the merchant-owned
|
|
499
|
-
Shopify app, checks it out to --dir, writes non-secret Shopify app
|
|
500
|
-
commits it, pushes it to Artifact Git, and records
|
|
501
|
-
|
|
502
|
-
|
|
519
|
+
Shopify app, checks it out to --dir, writes a minimal non-secret Shopify app
|
|
520
|
+
source tree, commits it, pushes it to Artifact Git, and records
|
|
521
|
+
sourceArtifactId on the connector.
|
|
522
|
+
|
|
523
|
+
The CLI bootstraps and deploys source. App blocks, web pixels, customer
|
|
524
|
+
metadata definitions, and app-owned GraphQL resources are source edits guided by
|
|
525
|
+
playbooks, not source init flags.
|
|
503
526
|
|
|
504
527
|
Examples:
|
|
505
|
-
tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --
|
|
528
|
+
tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --profile account --json`;
|
|
506
529
|
}
|
|
507
530
|
function connectorsShopifySourceCheckoutHelp() {
|
|
508
531
|
return `Usage: tender connectors shopify source checkout <connector-id> --dir <path> [--remote <name>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
@@ -526,11 +549,13 @@ function connectorsShopifySourceDeployHelp() {
|
|
|
526
549
|
return `Usage: tender connectors shopify source deploy <connector-id> [--dir <path>] [--client-id <shopify-client-id>] [--version <tag>] [--message <text>] [--source-control-url <url>] [--allow-deletes] [--confirm deploy] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
527
550
|
|
|
528
551
|
Without --confirm deploy, prints the exact Shopify CLI deployment command. With
|
|
529
|
-
--confirm deploy, runs npx --yes @shopify/cli@latest app deploy.
|
|
530
|
-
SHOPIFY_APP_AUTOMATION_TOKEN
|
|
552
|
+
--confirm deploy, runs npx --yes @shopify/cli@latest app deploy. If
|
|
553
|
+
SHOPIFY_APP_AUTOMATION_TOKEN is not set locally, the CLI requests the
|
|
554
|
+
connector's stored App Automation Token and injects it only into the spawned
|
|
555
|
+
Shopify CLI child process.
|
|
531
556
|
|
|
532
557
|
Examples:
|
|
533
|
-
|
|
558
|
+
tender connectors shopify source deploy conn_123 --dir ./shopify-app --version connector-source-smoke --confirm deploy --profile account --json`;
|
|
534
559
|
}
|
|
535
560
|
function connectorsShopifyDeleteHelp() {
|
|
536
561
|
return `Usage: tender connectors shopify delete <connector-id> --confirm delete [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
@@ -1509,6 +1534,10 @@ function tenderCliCapabilities() {
|
|
|
1509
1534
|
targetPackId: 'shopify-connector-source',
|
|
1510
1535
|
visibility: 'internal_source_artifact_not_customer_facing_app',
|
|
1511
1536
|
},
|
|
1537
|
+
sourceModel: {
|
|
1538
|
+
cliOwns: ['source artifact lifecycle', 'checkout', 'Shopify CLI validate/deploy forwarding'],
|
|
1539
|
+
playbooksOwn: ['Theme App Extension app blocks', 'web pixels', 'customer metadata definitions', 'Admin GraphQL resource shape'],
|
|
1540
|
+
},
|
|
1512
1541
|
guardrails: [
|
|
1513
1542
|
'Use the account profile because connectors are account-level resources.',
|
|
1514
1543
|
'Use minimum Shopify scopes for the requested job.',
|
|
@@ -1584,6 +1613,7 @@ function tenderCliCapabilities() {
|
|
|
1584
1613
|
'Connectors are tenant-owned platform bindings, not generated binding workers.',
|
|
1585
1614
|
'The merchant owns the Shopify custom app; Tender stores connector credentials and app source metadata.',
|
|
1586
1615
|
'Use connector.sourceProject.sourceArtifactId as the durable Shopify app source identity.',
|
|
1616
|
+
'Use source init for bootstrap only; app blocks, pixels, metadata definitions, and Admin GraphQL operations are source edits guided by playbooks.',
|
|
1587
1617
|
'Fetch shopify-connector-customer-metadata-widget when the requested job involves customer metafields, metaobjects, or profile writes.',
|
|
1588
1618
|
],
|
|
1589
1619
|
},
|
|
@@ -3560,18 +3590,11 @@ function parseConnectorsShopifySourceInitArgs(args) {
|
|
|
3560
3590
|
if (args.includes('--help') || args.includes('-h')) {
|
|
3561
3591
|
return { command: 'help', topic: 'connectors shopify source init' };
|
|
3562
3592
|
}
|
|
3563
|
-
const connectorId = parseConnectorId(args[0], 'tender connectors shopify source init <connector-id> --dir ./shopify-app --client-id abc --
|
|
3593
|
+
const connectorId = parseConnectorId(args[0], 'tender connectors shopify source init <connector-id> --dir ./shopify-app --client-id abc --json');
|
|
3564
3594
|
const parsed = parseConnectorsCommonArgs(args, 1);
|
|
3565
3595
|
let dir = null;
|
|
3566
3596
|
let clientId = null;
|
|
3567
3597
|
let sourceName = null;
|
|
3568
|
-
let tenderAppUrl = null;
|
|
3569
|
-
let tenderElement = null;
|
|
3570
|
-
let artifactId = null;
|
|
3571
|
-
let publicId = null;
|
|
3572
|
-
let themeExtensionHandle = 'tender-widget';
|
|
3573
|
-
let themeExtensionUid = null;
|
|
3574
|
-
let blockName = 'Tender Prompt Widget';
|
|
3575
3598
|
let scopes = null;
|
|
3576
3599
|
let force = false;
|
|
3577
3600
|
for (let index = 0; index < parsed.remaining.length; index += 1) {
|
|
@@ -3591,41 +3614,6 @@ function parseConnectorsShopifySourceInitArgs(args) {
|
|
|
3591
3614
|
index += 1;
|
|
3592
3615
|
continue;
|
|
3593
3616
|
}
|
|
3594
|
-
if (arg === '--tender-app-url') {
|
|
3595
|
-
tenderAppUrl = parseRequiredFlagValue(parsed.remaining[index + 1], '--tender-app-url');
|
|
3596
|
-
index += 1;
|
|
3597
|
-
continue;
|
|
3598
|
-
}
|
|
3599
|
-
if (arg === '--tender-element') {
|
|
3600
|
-
tenderElement = parseRequiredFlagValue(parsed.remaining[index + 1], '--tender-element');
|
|
3601
|
-
index += 1;
|
|
3602
|
-
continue;
|
|
3603
|
-
}
|
|
3604
|
-
if (arg === '--artifact-id') {
|
|
3605
|
-
artifactId = parseRequiredFlagValue(parsed.remaining[index + 1], '--artifact-id');
|
|
3606
|
-
index += 1;
|
|
3607
|
-
continue;
|
|
3608
|
-
}
|
|
3609
|
-
if (arg === '--public-id') {
|
|
3610
|
-
publicId = parseRequiredFlagValue(parsed.remaining[index + 1], '--public-id');
|
|
3611
|
-
index += 1;
|
|
3612
|
-
continue;
|
|
3613
|
-
}
|
|
3614
|
-
if (arg === '--theme-extension-handle') {
|
|
3615
|
-
themeExtensionHandle = parseRequiredFlagValue(parsed.remaining[index + 1], '--theme-extension-handle');
|
|
3616
|
-
index += 1;
|
|
3617
|
-
continue;
|
|
3618
|
-
}
|
|
3619
|
-
if (arg === '--theme-extension-uid') {
|
|
3620
|
-
themeExtensionUid = parseRequiredFlagValue(parsed.remaining[index + 1], '--theme-extension-uid');
|
|
3621
|
-
index += 1;
|
|
3622
|
-
continue;
|
|
3623
|
-
}
|
|
3624
|
-
if (arg === '--block-name') {
|
|
3625
|
-
blockName = parseRequiredFlagValue(parsed.remaining[index + 1], '--block-name');
|
|
3626
|
-
index += 1;
|
|
3627
|
-
continue;
|
|
3628
|
-
}
|
|
3629
3617
|
if (arg === '--scopes') {
|
|
3630
3618
|
scopes = parseRequiredFlagValue(parsed.remaining[index + 1], '--scopes');
|
|
3631
3619
|
index += 1;
|
|
@@ -3638,31 +3626,17 @@ function parseConnectorsShopifySourceInitArgs(args) {
|
|
|
3638
3626
|
throw new TenderCliUsageError(`Unknown connectors shopify source init option: ${arg}`);
|
|
3639
3627
|
}
|
|
3640
3628
|
if (!dir) {
|
|
3641
|
-
throw new TenderCliUsageError('--dir is required. Example: tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --
|
|
3629
|
+
throw new TenderCliUsageError('--dir is required. Example: tender connectors shopify source init conn_123 --dir ./shopify-app --client-id abc --json');
|
|
3642
3630
|
}
|
|
3643
3631
|
if (!clientId) {
|
|
3644
3632
|
throw new TenderCliUsageError('--client-id is required because Tender stores only the non-secret client ID prefix.');
|
|
3645
3633
|
}
|
|
3646
|
-
if (!tenderAppUrl) {
|
|
3647
|
-
throw new TenderCliUsageError('--tender-app-url is required. Pass the Tender app client.js URL to load in the app block.');
|
|
3648
|
-
}
|
|
3649
|
-
if (!tenderElement) {
|
|
3650
|
-
throw new TenderCliUsageError('--tender-element is required. Pass the Tender custom element tag to mount in Shopify.');
|
|
3651
|
-
}
|
|
3652
3634
|
return {
|
|
3653
3635
|
command: 'connectors shopify source init',
|
|
3654
3636
|
connectorId,
|
|
3655
3637
|
dir,
|
|
3656
3638
|
clientId,
|
|
3657
3639
|
sourceName,
|
|
3658
|
-
template: 'dog-profile',
|
|
3659
|
-
tenderAppUrl,
|
|
3660
|
-
tenderElement,
|
|
3661
|
-
artifactId,
|
|
3662
|
-
publicId,
|
|
3663
|
-
themeExtensionHandle,
|
|
3664
|
-
themeExtensionUid,
|
|
3665
|
-
blockName,
|
|
3666
3640
|
scopes,
|
|
3667
3641
|
force,
|
|
3668
3642
|
...parsed,
|
|
@@ -7562,7 +7536,7 @@ async function requestConnectorsApi(input) {
|
|
|
7562
7536
|
const message = (typeof payload?.message === 'string' && payload.message) ||
|
|
7563
7537
|
(typeof payload?.reasonCode === 'string' && payload.reasonCode) ||
|
|
7564
7538
|
`Connector request failed with HTTP ${response.status}.`;
|
|
7565
|
-
throw new
|
|
7539
|
+
throw new TenderCliConnectorsApiError([message, `Reason: ${reasonCode}.`, 'Example: tender connectors list --profile account --json.'].join('\n'), reasonCode, response.status, payload ?? null);
|
|
7566
7540
|
}
|
|
7567
7541
|
return payload;
|
|
7568
7542
|
}
|
|
@@ -8958,6 +8932,7 @@ function defaultCommandRunner(command, args, options) {
|
|
|
8958
8932
|
return new Promise((resolve, reject) => {
|
|
8959
8933
|
const child = spawn(command, args, {
|
|
8960
8934
|
cwd: options.cwd,
|
|
8935
|
+
...(options.env ? { env: options.env } : {}),
|
|
8961
8936
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
8962
8937
|
});
|
|
8963
8938
|
const stdout = [];
|
|
@@ -11136,20 +11111,6 @@ async function runConnectorsShopifyVerify(command, io, runtime) {
|
|
|
11136
11111
|
function requireShopifySourceString(value, fallback = '') {
|
|
11137
11112
|
return typeof value === 'string' && value.trim() ? value.trim() : fallback;
|
|
11138
11113
|
}
|
|
11139
|
-
function normalizeTenderElementTag(value) {
|
|
11140
|
-
const normalized = value.trim().toLowerCase();
|
|
11141
|
-
if (!/^[a-z][a-z0-9]*(?:-[a-z0-9]+)+$/.test(normalized)) {
|
|
11142
|
-
throw new TenderCliUsageError('--tender-element must be a valid custom element tag, for example tender-dog-profile.');
|
|
11143
|
-
}
|
|
11144
|
-
return normalized;
|
|
11145
|
-
}
|
|
11146
|
-
function normalizeThemeExtensionHandle(value) {
|
|
11147
|
-
const normalized = value.trim().toLowerCase();
|
|
11148
|
-
if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(normalized)) {
|
|
11149
|
-
throw new TenderCliUsageError('--theme-extension-handle must contain only lowercase letters, numbers, and dashes.');
|
|
11150
|
-
}
|
|
11151
|
-
return normalized;
|
|
11152
|
-
}
|
|
11153
11114
|
function tomlString(value) {
|
|
11154
11115
|
return JSON.stringify(value);
|
|
11155
11116
|
}
|
|
@@ -11159,213 +11120,30 @@ function uniqueSortedScopes(scopes) {
|
|
|
11159
11120
|
function parseScopeCsv(value) {
|
|
11160
11121
|
return uniqueSortedScopes((value ?? '').split(/[,\s]+/));
|
|
11161
11122
|
}
|
|
11162
|
-
function
|
|
11123
|
+
function defaultShopifySourceScopes(connectorScopes, override) {
|
|
11163
11124
|
if (override) {
|
|
11164
11125
|
return parseScopeCsv(override);
|
|
11165
11126
|
}
|
|
11166
11127
|
const fromConnector = Array.isArray(connectorScopes) ? connectorScopes.filter((scope) => typeof scope === 'string') : [];
|
|
11167
|
-
return uniqueSortedScopes(
|
|
11168
|
-
...fromConnector,
|
|
11169
|
-
'read_customers',
|
|
11170
|
-
'write_customers',
|
|
11171
|
-
'read_metaobject_definitions',
|
|
11172
|
-
'write_metaobject_definitions',
|
|
11173
|
-
'read_metaobjects',
|
|
11174
|
-
'write_metaobjects',
|
|
11175
|
-
]);
|
|
11176
|
-
}
|
|
11177
|
-
function tenderWidgetAssetJs() {
|
|
11178
|
-
return `(function () {
|
|
11179
|
-
const loader = (window.TenderPromptBlockLoader ||= {});
|
|
11180
|
-
loader.scripts ||= new Map();
|
|
11181
|
-
loader.stylesheets ||= new Map();
|
|
11182
|
-
|
|
11183
|
-
function loadScript(src) {
|
|
11184
|
-
if (!src) return Promise.reject(new Error("Missing Tender runtime URL"));
|
|
11185
|
-
|
|
11186
|
-
const existing = loader.scripts.get(src);
|
|
11187
|
-
if (existing) return existing.promise;
|
|
11188
|
-
|
|
11189
|
-
const promise = new Promise((resolve, reject) => {
|
|
11190
|
-
const script = document.createElement("script");
|
|
11191
|
-
script.type = "module";
|
|
11192
|
-
script.src = src;
|
|
11193
|
-
script.onload = resolve;
|
|
11194
|
-
script.onerror = () => reject(new Error(\`Failed to load \${src}\`));
|
|
11195
|
-
document.head.appendChild(script);
|
|
11196
|
-
});
|
|
11197
|
-
|
|
11198
|
-
loader.scripts.set(src, { status: "loading", promise });
|
|
11199
|
-
promise.then(
|
|
11200
|
-
() => loader.scripts.set(src, { status: "loaded", promise }),
|
|
11201
|
-
() => loader.scripts.delete(src),
|
|
11202
|
-
);
|
|
11203
|
-
|
|
11204
|
-
return promise;
|
|
11205
|
-
}
|
|
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
|
-
|
|
11297
|
-
class TenderPromptWidgetBlock extends HTMLElement {
|
|
11298
|
-
connectedCallback() {
|
|
11299
|
-
this.mount();
|
|
11300
|
-
}
|
|
11301
|
-
|
|
11302
|
-
async mount() {
|
|
11303
|
-
const mount = this.querySelector("[data-tender-mount]");
|
|
11304
|
-
const elementTag = this.dataset.tenderElementTag;
|
|
11305
|
-
if (!mount || !elementTag || mount.dataset.tenderMounted === "true") return;
|
|
11306
|
-
|
|
11307
|
-
mount.setAttribute("aria-busy", "true");
|
|
11308
|
-
preloadStylesheet(this.dataset.stylesheetUrl);
|
|
11309
|
-
await loadScript(this.dataset.tenderRuntimeSrc);
|
|
11310
|
-
await Promise.race([
|
|
11311
|
-
customElements.whenDefined(elementTag),
|
|
11312
|
-
new Promise((resolve) => setTimeout(resolve, 1000)),
|
|
11313
|
-
]);
|
|
11314
|
-
|
|
11315
|
-
const widget = document.createElement(elementTag);
|
|
11316
|
-
for (const [key, value] of Object.entries(this.dataset)) {
|
|
11317
|
-
if (value == null || key === "tenderRuntimeSrc" || key === "tenderElementTag") continue;
|
|
11318
|
-
widget.dataset[key] = value;
|
|
11319
|
-
}
|
|
11320
|
-
|
|
11321
|
-
widget.style.visibility = "hidden";
|
|
11322
|
-
widget.style.position = "absolute";
|
|
11323
|
-
widget.style.inset = "0";
|
|
11324
|
-
widget.style.pointerEvents = "none";
|
|
11325
|
-
mount.appendChild(widget);
|
|
11326
|
-
mount.dataset.tenderMounted = "true";
|
|
11327
|
-
await waitForWidgetPaintReady(widget, this.dataset.stylesheetUrl);
|
|
11328
|
-
revealWidget(mount, widget);
|
|
11329
|
-
}
|
|
11330
|
-
}
|
|
11331
|
-
|
|
11332
|
-
if (!customElements.get("tender-prompt-widget-block")) {
|
|
11333
|
-
customElements.define("tender-prompt-widget-block", TenderPromptWidgetBlock);
|
|
11334
|
-
}
|
|
11335
|
-
})();
|
|
11336
|
-
`;
|
|
11128
|
+
return uniqueSortedScopes(fromConnector);
|
|
11337
11129
|
}
|
|
11338
11130
|
function buildShopifySourceFiles(input) {
|
|
11339
11131
|
const appName = requireShopifySourceString(input.connector.appName, 'Tender Prompt Apps');
|
|
11340
11132
|
const adminApiVersion = requireShopifySourceString(input.connector.adminApiVersion, '2026-04');
|
|
11341
11133
|
const shopDomain = requireShopifySourceString(input.connector.shopDomain);
|
|
11342
|
-
const
|
|
11343
|
-
const extensionHandle = normalizeThemeExtensionHandle(input.themeExtensionHandle);
|
|
11344
|
-
const scopes = defaultDogProfileScopes(input.connector.scopes, input.scopes);
|
|
11345
|
-
const artifactId = input.artifactId ?? 'artifact_replace_me';
|
|
11346
|
-
const publicId = input.publicId ?? 'tender-widget';
|
|
11347
|
-
const stylesheetUrl = new URL('./styles.css', input.tenderAppUrl).href;
|
|
11348
|
-
const blockName = input.blockName.trim().slice(0, 120) || 'Tender Prompt Widget';
|
|
11134
|
+
const scopes = defaultShopifySourceScopes(input.connector.scopes, input.scopes);
|
|
11349
11135
|
const connectorManifest = {
|
|
11350
11136
|
version: 1,
|
|
11351
11137
|
kind: 'shopify_connector_source',
|
|
11352
11138
|
connectorId: input.connectorId,
|
|
11353
11139
|
instanceId: requireShopifySourceString(input.connector.instanceId, 'default'),
|
|
11354
|
-
template: input.template,
|
|
11355
11140
|
shopDomain: shopDomain || null,
|
|
11356
11141
|
adminApiVersion,
|
|
11357
11142
|
clientId: input.clientId,
|
|
11358
|
-
|
|
11359
|
-
tender: {
|
|
11360
|
-
artifactId,
|
|
11361
|
-
publicId,
|
|
11362
|
-
elementTag: tenderElement,
|
|
11363
|
-
runtimeUrl: input.tenderAppUrl,
|
|
11364
|
-
stylesheetUrl,
|
|
11365
|
-
},
|
|
11143
|
+
scopes,
|
|
11366
11144
|
secrets: {
|
|
11367
11145
|
clientSecret: 'stored in Tender connector credentials, not in this source tree',
|
|
11368
|
-
appAutomationToken: '
|
|
11146
|
+
appAutomationToken: 'stored in Tender connector secret store, not in this source tree',
|
|
11369
11147
|
},
|
|
11370
11148
|
};
|
|
11371
11149
|
const shopifyAppToml = `# Merchant-owned Shopify app source generated by Tender Prompt.
|
|
@@ -11387,170 +11165,31 @@ api_version = ${tomlString(adminApiVersion)}
|
|
|
11387
11165
|
|
|
11388
11166
|
[build]
|
|
11389
11167
|
automatically_update_urls_on_dev = true
|
|
11390
|
-
|
|
11391
|
-
[metaobjects.app.dog_profile]
|
|
11392
|
-
name = "Dog profile"
|
|
11393
|
-
display_name_field = "dog_name"
|
|
11394
|
-
access.admin = "merchant_read_write"
|
|
11395
|
-
|
|
11396
|
-
[metaobjects.app.dog_profile.fields.dog_name]
|
|
11397
|
-
name = "Dog name"
|
|
11398
|
-
type = "single_line_text_field"
|
|
11399
|
-
required = true
|
|
11400
|
-
|
|
11401
|
-
[metaobjects.app.dog_profile.fields.preferences]
|
|
11402
|
-
name = "Preferences"
|
|
11403
|
-
type = "json"
|
|
11404
|
-
|
|
11405
|
-
[customer.metafields.app.dog_profile]
|
|
11406
|
-
name = "Dog profile"
|
|
11407
|
-
type = "metaobject_reference<$app:dog_profile>"
|
|
11408
|
-
access.admin = "merchant_read_write"
|
|
11409
|
-
`;
|
|
11410
|
-
const extensionToml = [
|
|
11411
|
-
`name = ${tomlString(extensionHandle)}`,
|
|
11412
|
-
'type = "theme"',
|
|
11413
|
-
input.themeExtensionUid ? `uid = ${tomlString(input.themeExtensionUid)}` : null,
|
|
11414
|
-
'',
|
|
11415
|
-
]
|
|
11416
|
-
.filter((line) => line !== null)
|
|
11417
|
-
.join('\n');
|
|
11418
|
-
const liquid = `{% doc %}
|
|
11419
|
-
Tender Prompt app block generated from connector ${input.connectorId}.
|
|
11420
|
-
|
|
11421
|
-
The block loads one Tender runtime script and mounts the configured Tender
|
|
11422
|
-
custom element. Keep merchant-editable copy and version pins in block settings.
|
|
11423
|
-
{% enddoc %}
|
|
11424
|
-
|
|
11425
|
-
{% liquid
|
|
11426
|
-
assign artifact_id = block.settings.artifact_id | default: ${tomlString(artifactId)}
|
|
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
|
|
11434
|
-
%}
|
|
11435
|
-
|
|
11436
|
-
<tender-prompt-widget-block
|
|
11437
|
-
{{ block.shopify_attributes }}
|
|
11438
|
-
class="tender-prompt-widget-block tender-prompt-widget-block--{{ block.settings.tone }}"
|
|
11439
|
-
data-tender-artifact-id="{{ artifact_id | escape }}"
|
|
11440
|
-
data-tender-public-id="{{ block.settings.public_id | escape }}"
|
|
11441
|
-
data-tender-version="{{ widget_version | escape }}"
|
|
11442
|
-
data-tender-runtime-src="{{ runtime_src | escape }}"
|
|
11443
|
-
data-stylesheet-url="{{ stylesheet_src | escape }}"
|
|
11444
|
-
data-tender-element-tag="{{ block.settings.element_tag | escape }}"
|
|
11445
|
-
data-tender-mode="shopify_app_block"
|
|
11446
|
-
data-surface="shopify_theme_app_extension"
|
|
11447
|
-
data-tender-block-id="{{ block.id | escape }}"
|
|
11448
|
-
data-tender-shop-domain="{{ shop.permanent_domain | escape }}"
|
|
11449
|
-
data-tender-heading="{{ block.settings.heading | escape }}"
|
|
11450
|
-
data-tender-intro="{{ block.settings.intro | escape }}"
|
|
11451
|
-
data-tender-tone="{{ block.settings.tone | escape }}"
|
|
11452
|
-
{% if customer %}
|
|
11453
|
-
data-tender-customer-id="{{ customer.id }}"
|
|
11454
|
-
{% endif %}
|
|
11455
|
-
{% if product %}
|
|
11456
|
-
data-tender-product-id="{{ product.id }}"
|
|
11457
|
-
data-tender-product-handle="{{ product.handle | escape }}"
|
|
11458
|
-
{% endif %}
|
|
11459
|
-
>
|
|
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>
|
|
11475
|
-
</div>
|
|
11476
|
-
</tender-prompt-widget-block>
|
|
11477
|
-
|
|
11478
|
-
{% schema %}
|
|
11479
|
-
{
|
|
11480
|
-
"name": ${JSON.stringify(blockName)},
|
|
11481
|
-
"target": "section",
|
|
11482
|
-
"javascript": "tender-widget.js",
|
|
11483
|
-
"settings": [
|
|
11484
|
-
{
|
|
11485
|
-
"type": "text",
|
|
11486
|
-
"id": "element_tag",
|
|
11487
|
-
"label": "Tender custom element",
|
|
11488
|
-
"default": ${JSON.stringify(tenderElement)}
|
|
11489
|
-
},
|
|
11490
|
-
{
|
|
11491
|
-
"type": "text",
|
|
11492
|
-
"id": "artifact_id",
|
|
11493
|
-
"label": "Tender artifact ID",
|
|
11494
|
-
"default": ${JSON.stringify(artifactId)}
|
|
11495
|
-
},
|
|
11496
|
-
{
|
|
11497
|
-
"type": "text",
|
|
11498
|
-
"id": "public_id",
|
|
11499
|
-
"label": "Tender public ID",
|
|
11500
|
-
"default": ${JSON.stringify(publicId)}
|
|
11501
|
-
},
|
|
11502
|
-
{
|
|
11503
|
-
"type": "select",
|
|
11504
|
-
"id": "widget_version",
|
|
11505
|
-
"label": "Widget version",
|
|
11506
|
-
"options": [
|
|
11507
|
-
{ "value": "latest", "label": "Latest published version" }
|
|
11508
|
-
],
|
|
11509
|
-
"default": "latest"
|
|
11510
|
-
},
|
|
11511
|
-
{
|
|
11512
|
-
"type": "inline_richtext",
|
|
11513
|
-
"id": "heading",
|
|
11514
|
-
"label": "Heading",
|
|
11515
|
-
"default": "Tell us about your customer"
|
|
11516
|
-
},
|
|
11517
|
-
{
|
|
11518
|
-
"type": "text",
|
|
11519
|
-
"id": "intro",
|
|
11520
|
-
"label": "Intro",
|
|
11521
|
-
"default": "Answer a few quick questions and keep your profile up to date."
|
|
11522
|
-
},
|
|
11523
|
-
{
|
|
11524
|
-
"type": "select",
|
|
11525
|
-
"id": "tone",
|
|
11526
|
-
"label": "Tone",
|
|
11527
|
-
"options": [
|
|
11528
|
-
{ "value": "default", "label": "Default" },
|
|
11529
|
-
{ "value": "subtle", "label": "Subtle" },
|
|
11530
|
-
{ "value": "contrast", "label": "Contrast" }
|
|
11531
|
-
],
|
|
11532
|
-
"default": "default"
|
|
11533
|
-
}
|
|
11534
|
-
]
|
|
11535
|
-
}
|
|
11536
|
-
{% endschema %}
|
|
11537
11168
|
`;
|
|
11538
11169
|
const readme = `# ${appName}
|
|
11539
11170
|
|
|
11540
|
-
This is the merchant-owned Shopify app source
|
|
11171
|
+
This is the merchant-owned Shopify app source bootstrapped by Tender Prompt for
|
|
11541
11172
|
connector \`${input.connectorId}\`.
|
|
11542
11173
|
|
|
11543
11174
|
## What lives here
|
|
11544
11175
|
|
|
11545
|
-
- \`shopify.app.toml\`: Shopify app configuration
|
|
11546
|
-
- \`
|
|
11547
|
-
|
|
11176
|
+
- \`shopify.app.toml\`: Shopify app configuration and reviewed Admin API scopes.
|
|
11177
|
+
- \`connector.json\`: Non-secret Tender metadata that maps this source tree back
|
|
11178
|
+
to the connector.
|
|
11179
|
+
|
|
11180
|
+
## Add capabilities
|
|
11181
|
+
|
|
11182
|
+
This bootstrap intentionally does not generate app blocks, web pixels, customer
|
|
11183
|
+
metadata definitions, or app-owned GraphQL resources. Add those as source edits
|
|
11184
|
+
using the Shopify connector playbooks so the source tree stays the durable
|
|
11185
|
+
record of what the merchant-owned app actually contains.
|
|
11548
11186
|
|
|
11549
11187
|
## Secrets
|
|
11550
11188
|
|
|
11551
11189
|
Do not commit the Shopify client secret, Admin API token, or App Automation
|
|
11552
11190
|
Token. Runtime credentials stay in the Tender connector secret store. Deployment
|
|
11553
|
-
uses
|
|
11191
|
+
uses the connector's stored App Automation Token and passes it only to the
|
|
11192
|
+
spawned Shopify CLI child process.
|
|
11554
11193
|
|
|
11555
11194
|
## Validate
|
|
11556
11195
|
|
|
@@ -11561,21 +11200,16 @@ npx --yes @shopify/cli@latest app config validate --path . --json
|
|
|
11561
11200
|
## Deploy
|
|
11562
11201
|
|
|
11563
11202
|
\`\`\`sh
|
|
11564
|
-
|
|
11203
|
+
tender connectors shopify source deploy ${input.connectorId} --dir . --confirm deploy --profile account --json
|
|
11565
11204
|
\`\`\`
|
|
11566
11205
|
`;
|
|
11567
11206
|
return {
|
|
11568
|
-
extensionHandle,
|
|
11569
11207
|
manifest: connectorManifest,
|
|
11570
11208
|
files: new Map([
|
|
11571
11209
|
['.gitignore', '.shopify/\nnode_modules/\n.env\n.env.*\n'],
|
|
11572
11210
|
['README.md', readme],
|
|
11573
11211
|
['connector.json', `${JSON.stringify(connectorManifest, null, 2)}\n`],
|
|
11574
11212
|
['shopify.app.toml', shopifyAppToml],
|
|
11575
|
-
[`extensions/${extensionHandle}/shopify.extension.toml`, extensionToml],
|
|
11576
|
-
[`extensions/${extensionHandle}/blocks/tender_widget.liquid`, liquid],
|
|
11577
|
-
[`extensions/${extensionHandle}/assets/tender-widget.js`, tenderWidgetAssetJs()],
|
|
11578
|
-
[`extensions/${extensionHandle}/locales/en.default.json`, `${JSON.stringify({ tender: { block: { name: blockName } } }, null, 2)}\n`],
|
|
11579
11213
|
]),
|
|
11580
11214
|
};
|
|
11581
11215
|
}
|
|
@@ -11648,7 +11282,6 @@ async function patchConnectorSourceProject(input) {
|
|
|
11648
11282
|
version: 1,
|
|
11649
11283
|
kind: 'shopify_app_source',
|
|
11650
11284
|
sourceArtifactId: input.sourceArtifactId,
|
|
11651
|
-
template: input.template,
|
|
11652
11285
|
updatedAt: input.updatedAt,
|
|
11653
11286
|
};
|
|
11654
11287
|
const payload = await requestConnectorsApi({
|
|
@@ -11835,13 +11468,117 @@ function shopifyDeployArgs(input) {
|
|
|
11835
11468
|
function commandLine(command, args) {
|
|
11836
11469
|
return [command, ...args.map(shellQuote)].join(' ');
|
|
11837
11470
|
}
|
|
11471
|
+
function redactSensitiveText(value, secrets = []) {
|
|
11472
|
+
let redacted = value;
|
|
11473
|
+
for (const secret of secrets) {
|
|
11474
|
+
if (secret) {
|
|
11475
|
+
redacted = redacted.split(secret).join('[REDACTED]');
|
|
11476
|
+
}
|
|
11477
|
+
}
|
|
11478
|
+
return redacted
|
|
11479
|
+
.replace(/shpat_[A-Za-z0-9_:-]+/g, 'shpat_[REDACTED]')
|
|
11480
|
+
.replace(/\b(SHOPIFY_APP_AUTOMATION_TOKEN\s*[=:]\s*)([^\s'"]+)/gi, '$1[REDACTED]')
|
|
11481
|
+
.replace(/\b(authorization\s*[:=]\s*(?:Bearer\s+)?)([^\s'"]+)/gi, '$1[REDACTED]')
|
|
11482
|
+
.replace(/\b(x-shopify-access-token\s*[:=]\s*)([^\s'"]+)/gi, '$1[REDACTED]');
|
|
11483
|
+
}
|
|
11838
11484
|
async function runNpxCommand(input) {
|
|
11839
11485
|
const runner = input.runtime.commandRunner ?? defaultCommandRunner;
|
|
11840
|
-
const result = await runner('npx', input.args, {
|
|
11841
|
-
|
|
11842
|
-
|
|
11486
|
+
const result = await runner('npx', input.args, {
|
|
11487
|
+
cwd: input.cwd,
|
|
11488
|
+
...(input.env ? { env: input.env } : {}),
|
|
11489
|
+
});
|
|
11490
|
+
const redactedResult = {
|
|
11491
|
+
...result,
|
|
11492
|
+
stdout: redactSensitiveText(result.stdout, input.redactions),
|
|
11493
|
+
stderr: redactSensitiveText(result.stderr, input.redactions),
|
|
11494
|
+
};
|
|
11495
|
+
if (redactedResult.exitCode !== 0) {
|
|
11496
|
+
throw new Error([
|
|
11497
|
+
`npx ${input.args.join(' ')} failed with exit code ${redactedResult.exitCode}.`,
|
|
11498
|
+
redactedResult.stderr.trim() || redactedResult.stdout.trim(),
|
|
11499
|
+
]
|
|
11500
|
+
.filter(Boolean)
|
|
11501
|
+
.join('\n'));
|
|
11502
|
+
}
|
|
11503
|
+
return redactedResult;
|
|
11504
|
+
}
|
|
11505
|
+
async function fetchShopifyDeployToken(input) {
|
|
11506
|
+
const payload = await requestConnectorsApi({
|
|
11507
|
+
connectorsPath: `${encodeURIComponent(input.connectorId)}/shopify/deploy-token`,
|
|
11508
|
+
method: 'POST',
|
|
11509
|
+
baseUrl: input.credentials.baseUrl,
|
|
11510
|
+
token: input.credentials.token,
|
|
11511
|
+
fetcher: input.fetcher,
|
|
11512
|
+
body: {
|
|
11513
|
+
sourceArtifactId: input.sourceArtifactId,
|
|
11514
|
+
...(input.version ? { version: input.version } : {}),
|
|
11515
|
+
},
|
|
11516
|
+
});
|
|
11517
|
+
if (typeof payload.token !== 'string' || !payload.token.trim()) {
|
|
11518
|
+
throw new TenderCliUsageError('Tender did not return a Shopify deploy token. Re-run connector setup or store an App Automation Token.');
|
|
11519
|
+
}
|
|
11520
|
+
return {
|
|
11521
|
+
token: payload.token.trim(),
|
|
11522
|
+
expiresAt: typeof payload.expiresAt === 'number' ? payload.expiresAt : null,
|
|
11523
|
+
};
|
|
11524
|
+
}
|
|
11525
|
+
function localShopifyDeployToken(runtime) {
|
|
11526
|
+
const env = runtime.env ?? process.env;
|
|
11527
|
+
const token = env.SHOPIFY_APP_AUTOMATION_TOKEN;
|
|
11528
|
+
return token?.trim() ? token.trim() : null;
|
|
11529
|
+
}
|
|
11530
|
+
function childShopifyDeployEnv(runtime, token) {
|
|
11531
|
+
return {
|
|
11532
|
+
...process.env,
|
|
11533
|
+
...(runtime.env ?? {}),
|
|
11534
|
+
SHOPIFY_APP_AUTOMATION_TOKEN: token,
|
|
11535
|
+
};
|
|
11536
|
+
}
|
|
11537
|
+
function connectorsProfileFlag(command) {
|
|
11538
|
+
return command.profileName ? `--profile ${command.profileName}` : '--profile account';
|
|
11539
|
+
}
|
|
11540
|
+
function shopifyDeployTokenRecoveryMessage(command, reasonCode) {
|
|
11541
|
+
const profileFlag = connectorsProfileFlag(command);
|
|
11542
|
+
return [
|
|
11543
|
+
reasonCode === 'shopify_deploy_token_expired'
|
|
11544
|
+
? 'The connector stored Shopify App Automation Token is expired.'
|
|
11545
|
+
: 'The connector does not have a stored Shopify App Automation Token for source deploys.',
|
|
11546
|
+
`Store a fresh token: printf "$SHOPIFY_APP_AUTOMATION_TOKEN" | tender connectors shopify app-automation-token set ${command.connectorId} --token-stdin ${profileFlag} --json`,
|
|
11547
|
+
`Setup URL: tender connectors shopify setup-url ${command.connectorId} ${profileFlag} --json`,
|
|
11548
|
+
'Fallback: export SHOPIFY_APP_AUTOMATION_TOKEN locally before running source deploy.',
|
|
11549
|
+
].join('\n');
|
|
11550
|
+
}
|
|
11551
|
+
async function resolveShopifyDeployTokenEnvironment(input) {
|
|
11552
|
+
const localToken = localShopifyDeployToken(input.runtime);
|
|
11553
|
+
if (localToken) {
|
|
11554
|
+
return {
|
|
11555
|
+
env: childShopifyDeployEnv(input.runtime, localToken),
|
|
11556
|
+
redactions: [localToken],
|
|
11557
|
+
source: 'local-env',
|
|
11558
|
+
};
|
|
11559
|
+
}
|
|
11560
|
+
try {
|
|
11561
|
+
const handoff = await fetchShopifyDeployToken({
|
|
11562
|
+
connectorId: input.command.connectorId,
|
|
11563
|
+
sourceArtifactId: input.sourceArtifactId,
|
|
11564
|
+
version: input.command.version,
|
|
11565
|
+
credentials: input.credentials,
|
|
11566
|
+
fetcher: input.fetcher,
|
|
11567
|
+
});
|
|
11568
|
+
return {
|
|
11569
|
+
env: childShopifyDeployEnv(input.runtime, handoff.token),
|
|
11570
|
+
redactions: [handoff.token],
|
|
11571
|
+
source: 'stored-connector-token',
|
|
11572
|
+
};
|
|
11573
|
+
}
|
|
11574
|
+
catch (error) {
|
|
11575
|
+
if (error instanceof TenderCliConnectorsApiError &&
|
|
11576
|
+
(error.reasonCode === 'shopify_deploy_token_required' || error.reasonCode === 'shopify_deploy_token_expired')) {
|
|
11577
|
+
const recovery = shopifyDeployTokenRecoveryMessage(input.command, error.reasonCode);
|
|
11578
|
+
throw new TenderCliGuidedUsageError(recovery, recovery);
|
|
11579
|
+
}
|
|
11580
|
+
throw error;
|
|
11843
11581
|
}
|
|
11844
|
-
return result;
|
|
11845
11582
|
}
|
|
11846
11583
|
async function runConnectorsShopifySourceInit(command, io, runtime) {
|
|
11847
11584
|
const fetcher = runtime.fetcher ?? fetch;
|
|
@@ -11865,14 +11602,6 @@ async function runConnectorsShopifySourceInit(command, io, runtime) {
|
|
|
11865
11602
|
connectorId: command.connectorId,
|
|
11866
11603
|
connector,
|
|
11867
11604
|
clientId: command.clientId,
|
|
11868
|
-
template: command.template,
|
|
11869
|
-
tenderAppUrl: command.tenderAppUrl,
|
|
11870
|
-
tenderElement: command.tenderElement,
|
|
11871
|
-
artifactId: command.artifactId,
|
|
11872
|
-
publicId: command.publicId,
|
|
11873
|
-
themeExtensionHandle: command.themeExtensionHandle,
|
|
11874
|
-
themeExtensionUid: command.themeExtensionUid,
|
|
11875
|
-
blockName: command.blockName,
|
|
11876
11605
|
scopes: command.scopes,
|
|
11877
11606
|
});
|
|
11878
11607
|
const files = await writeShopifySourceFiles({ dir, files: source.files, force: command.force });
|
|
@@ -11887,7 +11616,6 @@ async function runConnectorsShopifySourceInit(command, io, runtime) {
|
|
|
11887
11616
|
const patched = await patchConnectorSourceProject({
|
|
11888
11617
|
connectorId: command.connectorId,
|
|
11889
11618
|
sourceArtifactId: sourceArtifact.sourceArtifactId,
|
|
11890
|
-
template: command.template,
|
|
11891
11619
|
credentials,
|
|
11892
11620
|
fetcher,
|
|
11893
11621
|
updatedAt: runtime.now ? runtime.now() : Date.now(),
|
|
@@ -11911,7 +11639,7 @@ async function runConnectorsShopifySourceInit(command, io, runtime) {
|
|
|
11911
11639
|
commit: gitCommit,
|
|
11912
11640
|
},
|
|
11913
11641
|
validateCommand: commandLine('npx', shopifyValidateArgs({ dir, clientId: command.clientId })),
|
|
11914
|
-
deployCommand: commandLine('npx', shopifyDeployArgs({ dir, clientId: command.clientId, version: '
|
|
11642
|
+
deployCommand: commandLine('npx', shopifyDeployArgs({ dir, clientId: command.clientId, version: 'connector-source', message: null, sourceControlUrl: null, allowDeletes: false })),
|
|
11915
11643
|
};
|
|
11916
11644
|
printConnectorsPayload(command, io, result, [
|
|
11917
11645
|
`connector_id: ${command.connectorId}`,
|
|
@@ -11920,6 +11648,7 @@ async function runConnectorsShopifySourceInit(command, io, runtime) {
|
|
|
11920
11648
|
`files: ${files.length}`,
|
|
11921
11649
|
`git_pushed: ${gitCommit.pushed ? 'true' : 'false'}`,
|
|
11922
11650
|
`next_validate: ${result.validateCommand}`,
|
|
11651
|
+
'next_source_edits: add app blocks, pixels, or metadata definitions from playbooks',
|
|
11923
11652
|
]);
|
|
11924
11653
|
return 0;
|
|
11925
11654
|
}
|
|
@@ -11980,7 +11709,8 @@ async function runConnectorsShopifySourceValidate(command, io, runtime) {
|
|
|
11980
11709
|
return 0;
|
|
11981
11710
|
}
|
|
11982
11711
|
async function runConnectorsShopifySourceDeploy(command, io, runtime) {
|
|
11983
|
-
const
|
|
11712
|
+
const fetcher = runtime.fetcher ?? fetch;
|
|
11713
|
+
const { credentials, connector } = await requestConnector(command, runtime);
|
|
11984
11714
|
const sourceArtifactId = readConnectorSourceArtifactId(connector);
|
|
11985
11715
|
if (!sourceArtifactId) {
|
|
11986
11716
|
throw new TenderCliUsageError('Connector does not have a source artifact yet. Run tender connectors shopify source init <connector-id> --dir ./shopify-app first.');
|
|
@@ -11998,7 +11728,14 @@ async function runConnectorsShopifySourceDeploy(command, io, runtime) {
|
|
|
11998
11728
|
printConnectorsPayload(command, io, { ok: true, dryRun: true, command: command.command, connectorId: command.connectorId, sourceArtifactId, dir, shopifyCommand: commandLine('npx', args), confirmRequired: 'deploy' }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, `shopify_command: ${commandLine('npx', args)}`, 'confirm_required: --confirm deploy']);
|
|
11999
11729
|
return 0;
|
|
12000
11730
|
}
|
|
12001
|
-
const
|
|
11731
|
+
const deployToken = await resolveShopifyDeployTokenEnvironment({
|
|
11732
|
+
command,
|
|
11733
|
+
sourceArtifactId,
|
|
11734
|
+
credentials,
|
|
11735
|
+
runtime,
|
|
11736
|
+
fetcher,
|
|
11737
|
+
});
|
|
11738
|
+
const result = await runNpxCommand({ args, cwd: dir, runtime, env: deployToken.env, redactions: deployToken.redactions });
|
|
12002
11739
|
printConnectorsPayload(command, io, { ok: true, command: command.command, connectorId: command.connectorId, sourceArtifactId, dir, exitCode: result.exitCode, stdout: result.stdout, stderr: result.stderr }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, 'deploy: completed']);
|
|
12003
11740
|
return 0;
|
|
12004
11741
|
}
|
|
@@ -12841,7 +12578,7 @@ function cliErrorMessage(error) {
|
|
|
12841
12578
|
return String(error);
|
|
12842
12579
|
}
|
|
12843
12580
|
function cliErrorCode(error) {
|
|
12844
|
-
if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError) {
|
|
12581
|
+
if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError || error instanceof TenderCliConnectorsApiError) {
|
|
12845
12582
|
return error.reasonCode;
|
|
12846
12583
|
}
|
|
12847
12584
|
if (error && typeof error === 'object' && 'code' in error) {
|
|
@@ -12916,6 +12653,9 @@ function exampleForError(error, args, command) {
|
|
|
12916
12653
|
return dbPath ? dbCorrectedExample(artifactId, dbPath) : null;
|
|
12917
12654
|
}
|
|
12918
12655
|
function nextActionForError(error) {
|
|
12656
|
+
if (error instanceof TenderCliGuidedUsageError) {
|
|
12657
|
+
return error.nextAction;
|
|
12658
|
+
}
|
|
12919
12659
|
return error instanceof TenderCliDbApiError ? error.nextAction : null;
|
|
12920
12660
|
}
|
|
12921
12661
|
function cliErrorPayload(error, args, command) {
|