@tenderprompt/cli 0.1.34 → 0.1.36
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 +0 -4
- package/dist/index.js +307 -730
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -32,6 +32,8 @@ function readCliPackageVersion() {
|
|
|
32
32
|
}
|
|
33
33
|
const CLI_PACKAGE_VERSION = readCliPackageVersion();
|
|
34
34
|
const GIT_COMMIT_SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
35
|
+
const SHOPIFY_CLI_PACKAGE = '@shopify/cli@latest';
|
|
36
|
+
const SHOPIFY_CLI_BIN = 'shopify';
|
|
35
37
|
const ANALYTICS_AUTHORING_PLAYBOOK_ID = 'analytics-event-authoring';
|
|
36
38
|
const ANALYTICS_AUTHORING_COMMAND = 'tender app analytics authoring --json';
|
|
37
39
|
const ANALYTICS_AUTHORING_PLAYBOOK_COMMAND = `tender playbooks get ${ANALYTICS_AUTHORING_PLAYBOOK_ID} --json`;
|
|
@@ -145,6 +147,23 @@ class TenderCliConnectorsApiError extends Error {
|
|
|
145
147
|
this.example = example;
|
|
146
148
|
}
|
|
147
149
|
}
|
|
150
|
+
class TenderCliChildProcessError extends Error {
|
|
151
|
+
command;
|
|
152
|
+
args;
|
|
153
|
+
exitCode;
|
|
154
|
+
stdout;
|
|
155
|
+
stderr;
|
|
156
|
+
constructor(input) {
|
|
157
|
+
const detail = input.stderr.trim() || input.stdout.trim();
|
|
158
|
+
super([`${input.command} ${input.args.join(' ')} failed with exit code ${input.exitCode}.`, detail].filter(Boolean).join('\n'));
|
|
159
|
+
this.name = 'TenderCliChildProcessError';
|
|
160
|
+
this.command = input.command;
|
|
161
|
+
this.args = input.args;
|
|
162
|
+
this.exitCode = input.exitCode;
|
|
163
|
+
this.stdout = input.stdout;
|
|
164
|
+
this.stderr = input.stderr;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
148
167
|
const DEFAULT_PROFILE_NAME = 'default';
|
|
149
168
|
const DEFAULT_BASE_URL = 'https://app.tenderprompt.com';
|
|
150
169
|
const ARTIFACT_SOURCE_EXPORT_MAX_FILES = 1000;
|
|
@@ -180,12 +199,6 @@ Commands:
|
|
|
180
199
|
tender connectors shopify verify
|
|
181
200
|
Exchange credentials for an Admin API access token
|
|
182
201
|
tender connectors bind Print an app.json binding for a connector instance
|
|
183
|
-
tender shopify workspace connectors list
|
|
184
|
-
List current-store Shopify connectors from a hosted workspace
|
|
185
|
-
tender shopify workspace link-runtime --connector <connector-id>
|
|
186
|
-
Link the hosted runtime to a Shopify connector
|
|
187
|
-
tender shopify workspace deploy-source
|
|
188
|
-
Deploy linked Shopify source from a hosted workspace
|
|
189
202
|
tender app list List Tender App records visible to the current token
|
|
190
203
|
tender app create --name <name>
|
|
191
204
|
Create a new Tender App in the current account
|
|
@@ -263,9 +276,6 @@ Examples:
|
|
|
263
276
|
printf "$SHOPIFY_APP_AUTOMATION_TOKEN" | tender connectors shopify app-automation-token set conn_123 --token-stdin --expires-at 2026-08-22 --profile account --json
|
|
264
277
|
tender connectors shopify verify conn_123 --profile account --json
|
|
265
278
|
tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json
|
|
266
|
-
tender shopify workspace connectors list --json
|
|
267
|
-
tender shopify workspace link-runtime --connector conn_123 --json
|
|
268
|
-
tender shopify workspace deploy-source --json
|
|
269
279
|
tender app list --json
|
|
270
280
|
tender app create --name "Exit Intent Widget" --json
|
|
271
281
|
tender app create --name "Data Collection App" --init --dir ./data-collection-app --preview --json
|
|
@@ -480,33 +490,6 @@ Examples:
|
|
|
480
490
|
tender connectors shopify source deploy conn_123 --dir ./shopify-app --confirm deploy --profile account --json
|
|
481
491
|
tender connectors bind SHOPIFY_CONNECTOR conn_123 --instance default --json`;
|
|
482
492
|
}
|
|
483
|
-
function shopifyWorkspaceHelp() {
|
|
484
|
-
return `Usage: tender shopify workspace <command> [--json]
|
|
485
|
-
|
|
486
|
-
Hosted Shopify workspace commands use the current terminal workspace identity.
|
|
487
|
-
They do not read Tender profiles, account-scoped tokens, Shopify client secrets,
|
|
488
|
-
or Shopify App Automation Tokens from the terminal.
|
|
489
|
-
|
|
490
|
-
Commands:
|
|
491
|
-
tender shopify workspace connectors list
|
|
492
|
-
List current-store Shopify connectors available to this runtime
|
|
493
|
-
tender shopify workspace link-runtime --connector <connector-id>
|
|
494
|
-
Link this runtime to a Shopify connector source project
|
|
495
|
-
tender shopify workspace validate-source
|
|
496
|
-
Validate the linked Shopify connector source
|
|
497
|
-
tender shopify workspace deploy-source
|
|
498
|
-
Deploy the linked Shopify connector source
|
|
499
|
-
|
|
500
|
-
Required environment:
|
|
501
|
-
TENDER_BASE_URL
|
|
502
|
-
TENDER_RUNTIME_ARTIFACT_ID
|
|
503
|
-
|
|
504
|
-
Examples:
|
|
505
|
-
tender shopify workspace connectors list --json
|
|
506
|
-
tender shopify workspace link-runtime --connector conn_123 --json
|
|
507
|
-
tender shopify workspace validate-source --json
|
|
508
|
-
tender shopify workspace deploy-source --json`;
|
|
509
|
-
}
|
|
510
493
|
function connectorsShopifyCreateHelp() {
|
|
511
494
|
return `Usage: tender connectors shopify create [--instance <id>] [--label <text>] [--app-name <text>] [--admin-api-version <version>] [--shop <myshopify-domain>] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
512
495
|
|
|
@@ -592,7 +575,10 @@ function connectorsShopifySourceValidateHelp() {
|
|
|
592
575
|
return `Usage: tender connectors shopify source validate <connector-id> [--dir <path>] [--client-id <shopify-client-id>] [--execute] [--base-url <url>] [--token <token>] [--profile <name>] [--json]
|
|
593
576
|
|
|
594
577
|
Without --execute, prints the exact Shopify CLI validation command. With
|
|
595
|
-
--execute, runs
|
|
578
|
+
the --execute flag, runs npm exec --yes --package @shopify/cli@latest -- shopify app config validate with npm lifecycle environment variables scrubbed. If
|
|
579
|
+
SHOPIFY_APP_AUTOMATION_TOKEN is not set locally, the CLI requests the
|
|
580
|
+
connector's stored App Automation Token and injects it only into the spawned
|
|
581
|
+
Shopify CLI child process.
|
|
596
582
|
|
|
597
583
|
Examples:
|
|
598
584
|
tender connectors shopify source validate conn_123 --dir ./shopify-app --execute --json`;
|
|
@@ -601,17 +587,14 @@ function connectorsShopifySourceDeployHelp() {
|
|
|
601
587
|
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]
|
|
602
588
|
|
|
603
589
|
Without --confirm deploy, prints the exact Shopify CLI deployment command. With
|
|
604
|
-
--confirm deploy, runs
|
|
590
|
+
the --confirm deploy flag, runs npm exec --yes --package @shopify/cli@latest -- shopify app deploy with npm lifecycle environment variables scrubbed. If
|
|
605
591
|
SHOPIFY_APP_AUTOMATION_TOKEN is not set locally, the CLI requests the
|
|
606
592
|
connector's stored App Automation Token and injects it only into the spawned
|
|
607
593
|
Shopify CLI child process.
|
|
608
594
|
|
|
609
|
-
Inside
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
stored App Automation Token server-side. Do not run this account-scoped command
|
|
613
|
-
with a hosted workspace artifact token. Never ask the merchant to paste token
|
|
614
|
-
material into the terminal.
|
|
595
|
+
Inside the Shopify app, deploy connector source with the explicit **Deploy
|
|
596
|
+
source** action after the chat editor validates the pushed commit. Never ask the
|
|
597
|
+
merchant to paste token material into chat or a terminal.
|
|
615
598
|
|
|
616
599
|
Examples:
|
|
617
600
|
tender connectors shopify source deploy conn_123 --dir ./shopify-app --version connector-source-smoke --confirm deploy --profile account --json`;
|
|
@@ -989,7 +972,8 @@ function analyticsChartsTemplatesHelp() {
|
|
|
989
972
|
|
|
990
973
|
Print reusable chart spec templates for agents. Adapt templates to observed
|
|
991
974
|
events, paths, and property keys from analytics capabilities before querying or
|
|
992
|
-
saving.
|
|
975
|
+
saving. The hourly_dimension_breakdown_24h template documents the bounded
|
|
976
|
+
hour-by-property operator report.
|
|
993
977
|
|
|
994
978
|
Examples:
|
|
995
979
|
tender app analytics charts templates --json`;
|
|
@@ -1636,7 +1620,6 @@ function tenderCliCapabilities() {
|
|
|
1636
1620
|
'tender app outbound-auth --help',
|
|
1637
1621
|
'tender connectors --help',
|
|
1638
1622
|
'tender connectors shopify source init --help',
|
|
1639
|
-
'tender shopify workspace --help',
|
|
1640
1623
|
],
|
|
1641
1624
|
},
|
|
1642
1625
|
workflows: [
|
|
@@ -1681,36 +1664,6 @@ function tenderCliCapabilities() {
|
|
|
1681
1664
|
'Fetch shopify-connector-customer-metadata-widget when the requested job involves customer metafields, metaobjects, or profile writes.',
|
|
1682
1665
|
],
|
|
1683
1666
|
},
|
|
1684
|
-
{
|
|
1685
|
-
name: 'hosted_shopify_project_release',
|
|
1686
|
-
when: 'Inside a hosted Shopify project workspace after editing a runtime app and linked Shopify connector source, or when the merchant asks to deploy/release/publish the Shopify connector.',
|
|
1687
|
-
commands: [
|
|
1688
|
-
'tender shopify workspace connectors list --json',
|
|
1689
|
-
'tender shopify workspace link-runtime --connector <connector-id> --json',
|
|
1690
|
-
'cd /workspace/runtime-project && git push origin HEAD:main',
|
|
1691
|
-
'cd /workspace/runtime-project && commit=$(git rev-parse HEAD) && tender app publish "$TENDER_RUNTIME_ARTIFACT_ID" --commit "$commit" --confirm publish --stream --json',
|
|
1692
|
-
'cd /workspace/shopify-connector && git push origin HEAD:main',
|
|
1693
|
-
'cd /workspace/shopify-connector && tender shopify workspace validate-source --json',
|
|
1694
|
-
'cd /workspace/shopify-connector && tender shopify workspace deploy-source --json',
|
|
1695
|
-
],
|
|
1696
|
-
notes: [
|
|
1697
|
-
'If /workspace/shopify-connector is missing, list connectors, link the selected connector, then continue in /workspace/shopify-connector unless link-runtime explicitly returns reloadRequired:true.',
|
|
1698
|
-
'For connector selection or switching, use tender shopify workspace connectors list --json and tender shopify workspace link-runtime --connector <connector-id> --json; do not use tender connectors list or ask for a Shopify Client ID.',
|
|
1699
|
-
'When selecting by merchant-provided Shopify app name, prefer an exact normalized label/appName match before any partial match; do not choose a longer connector name that merely contains the requested name.',
|
|
1700
|
-
'After link-runtime succeeds, tender shopify workspace validate-source and deploy-source automatically use that selected connector.',
|
|
1701
|
-
'For a deploy-only connector request, skip npm install, npm run check, tender app doctor, and other runtime-project checks; run cd /workspace/shopify-connector && tender shopify workspace deploy-source --json.',
|
|
1702
|
-
'For a validate-only connector request, run cd /workspace/shopify-connector && tender shopify workspace validate-source --json.',
|
|
1703
|
-
'Only run runtime-project checks when the merchant asks to change, validate, preview, or publish the runtime app.',
|
|
1704
|
-
'Publish the Tender runtime first and use the pinned runtime URL from publish JSON to update the linked connector source before deploying Shopify.',
|
|
1705
|
-
'Use TENDER_SHOPIFY_CONNECTOR_ID or connector.json for Tender connector commands; shopify.app.toml contains the Shopify client_id, not the Tender connector identity.',
|
|
1706
|
-
'Do not run tender connectors shopify source validate or tender connectors shopify source deploy from hosted workspaces; those commands require an account-scoped Tender CLI token.',
|
|
1707
|
-
'Use tender shopify workspace validate-source and deploy-source so the connector stored App Automation Token stays server-side.',
|
|
1708
|
-
'If Shopify CLI is already authenticated in the hosted workspace, direct shopify app config validate and shopify app deploy are allowed.',
|
|
1709
|
-
'Never ask for Shopify App Automation Tokens, client secrets, Tender tokens, or provider keys in the terminal.',
|
|
1710
|
-
'Do not release by pushing Git tags; Tender runtime publish plus tender shopify workspace deploy-source are the release path.',
|
|
1711
|
-
'If tender shopify workspace deploy-source reports a missing or expired App Automation Token, stop and ask the merchant to refresh the Custom App automation token in Tender.',
|
|
1712
|
-
],
|
|
1713
|
-
},
|
|
1714
1667
|
{
|
|
1715
1668
|
name: 'create_new_app_with_preview',
|
|
1716
1669
|
when: 'When the user wants a new Tender App that is immediately visible.',
|
|
@@ -2108,6 +2061,25 @@ function normalizeBaseUrl(value) {
|
|
|
2108
2061
|
function readDefaultBaseUrl(env) {
|
|
2109
2062
|
return normalizeBaseUrl(env.TENDER_BASE_URL ?? DEFAULT_BASE_URL);
|
|
2110
2063
|
}
|
|
2064
|
+
function readDefaultShopifyApprovalContext(env) {
|
|
2065
|
+
const shopifyAppUrl = env.TENDER_SHOPIFY_APP_URL?.trim();
|
|
2066
|
+
if (shopifyAppUrl) {
|
|
2067
|
+
const parsed = parseShopifyAppUrl(shopifyAppUrl, 'TENDER_SHOPIFY_APP_URL');
|
|
2068
|
+
return {
|
|
2069
|
+
shopifyAppUrl: parsed.appUrl,
|
|
2070
|
+
shopifyStore: parsed.store,
|
|
2071
|
+
};
|
|
2072
|
+
}
|
|
2073
|
+
const shopifyStore = env.TENDER_SHOPIFY_STORE?.trim();
|
|
2074
|
+
if (shopifyStore) {
|
|
2075
|
+
const store = parseShopifyStoreHandle(shopifyStore, 'TENDER_SHOPIFY_STORE');
|
|
2076
|
+
return {
|
|
2077
|
+
shopifyAppUrl: shopifyAppUrlForStore(store),
|
|
2078
|
+
shopifyStore: store,
|
|
2079
|
+
};
|
|
2080
|
+
}
|
|
2081
|
+
return null;
|
|
2082
|
+
}
|
|
2111
2083
|
function readConfigPath(env) {
|
|
2112
2084
|
return env.TENDER_CONFIG ? path.resolve(env.TENDER_CONFIG) : path.join(os.homedir(), '.tender', 'config.json');
|
|
2113
2085
|
}
|
|
@@ -2299,17 +2271,6 @@ async function resolveApiCredentials(input) {
|
|
|
2299
2271
|
};
|
|
2300
2272
|
}
|
|
2301
2273
|
async function resolveLifecycleApiCredentials(input) {
|
|
2302
|
-
const baseUrl = normalizeBaseUrl(input.baseUrl ?? input.env.TENDER_BASE_URL ?? DEFAULT_BASE_URL);
|
|
2303
|
-
if (!input.flagToken &&
|
|
2304
|
-
!input.profileName &&
|
|
2305
|
-
isHostedWorkspaceInternalApiBaseUrl(baseUrl)) {
|
|
2306
|
-
return {
|
|
2307
|
-
baseUrl,
|
|
2308
|
-
token: null,
|
|
2309
|
-
source: 'hosted-workspace',
|
|
2310
|
-
profileName: null,
|
|
2311
|
-
};
|
|
2312
|
-
}
|
|
2313
2274
|
return await resolveApiCredentials(input);
|
|
2314
2275
|
}
|
|
2315
2276
|
function apiHeadersForCredentials(input) {
|
|
@@ -4017,59 +3978,18 @@ function parseConnectorsShopifySourceDeployArgs(args) {
|
|
|
4017
3978
|
}
|
|
4018
3979
|
throw new TenderCliUsageError(`Unknown connectors shopify source deploy option: ${arg}`);
|
|
4019
3980
|
}
|
|
4020
|
-
return {
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
throw new TenderCliUsageError(`Unknown ${command} option: ${arg}`);
|
|
4033
|
-
}
|
|
4034
|
-
return { command, json };
|
|
4035
|
-
}
|
|
4036
|
-
function parseShopifyWorkspaceConnectorsListArgs(args) {
|
|
4037
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
4038
|
-
return { command: 'help', topic: 'shopify workspace connectors list' };
|
|
4039
|
-
}
|
|
4040
|
-
let json = false;
|
|
4041
|
-
for (const arg of args) {
|
|
4042
|
-
if (arg === '--json') {
|
|
4043
|
-
json = true;
|
|
4044
|
-
continue;
|
|
4045
|
-
}
|
|
4046
|
-
throw new TenderCliUsageError(`Unknown shopify workspace connectors list option: ${arg}`);
|
|
4047
|
-
}
|
|
4048
|
-
return { command: 'shopify workspace connectors list', json };
|
|
4049
|
-
}
|
|
4050
|
-
function parseShopifyWorkspaceLinkRuntimeArgs(args) {
|
|
4051
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
4052
|
-
return { command: 'help', topic: 'shopify workspace link-runtime' };
|
|
4053
|
-
}
|
|
4054
|
-
let connectorId = null;
|
|
4055
|
-
let json = false;
|
|
4056
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
4057
|
-
const arg = args[index];
|
|
4058
|
-
if (arg === '--json') {
|
|
4059
|
-
json = true;
|
|
4060
|
-
continue;
|
|
4061
|
-
}
|
|
4062
|
-
if (arg === '--connector') {
|
|
4063
|
-
connectorId = parseConnectorId(parseRequiredFlagValue(args[index + 1], '--connector'), 'tender shopify workspace link-runtime --connector conn_123 --json');
|
|
4064
|
-
index += 1;
|
|
4065
|
-
continue;
|
|
4066
|
-
}
|
|
4067
|
-
throw new TenderCliUsageError(`Unknown shopify workspace link-runtime option: ${arg}`);
|
|
4068
|
-
}
|
|
4069
|
-
if (!connectorId) {
|
|
4070
|
-
throw new TenderCliUsageError('--connector is required. Example: tender shopify workspace link-runtime --connector conn_123 --json');
|
|
4071
|
-
}
|
|
4072
|
-
return { command: 'shopify workspace link-runtime', connectorId, json };
|
|
3981
|
+
return {
|
|
3982
|
+
command: 'connectors shopify source deploy',
|
|
3983
|
+
connectorId,
|
|
3984
|
+
dir,
|
|
3985
|
+
clientId,
|
|
3986
|
+
version,
|
|
3987
|
+
message,
|
|
3988
|
+
sourceControlUrl,
|
|
3989
|
+
allowDeletes,
|
|
3990
|
+
confirm,
|
|
3991
|
+
...parsed,
|
|
3992
|
+
};
|
|
4073
3993
|
}
|
|
4074
3994
|
function parseConnectorsShopifyDeleteArgs(args) {
|
|
4075
3995
|
if (args.includes('--help') || args.includes('-h')) {
|
|
@@ -5595,6 +5515,10 @@ function analyticsAuthoringContract() {
|
|
|
5595
5515
|
},
|
|
5596
5516
|
],
|
|
5597
5517
|
charting: {
|
|
5518
|
+
contractDiscovery: {
|
|
5519
|
+
capabilities: 'tender app analytics capabilities <app-id> --include-catalog --json',
|
|
5520
|
+
templates: 'tender app analytics charts templates --json',
|
|
5521
|
+
},
|
|
5598
5522
|
starterDashboardCommands: ANALYTICS_AGENT_GUIDANCE.starterDashboardCommands,
|
|
5599
5523
|
completionProof: ANALYTICS_AGENT_GUIDANCE.dashboardCompletion.proofCommand,
|
|
5600
5524
|
unitFunnel: 'After traffic exists, use tender app analytics capabilities <app-id> --include-catalog --json and create version 2 unit_step_count funnel specs from observedFlows and observedUnitTypes.',
|
|
@@ -5832,6 +5756,33 @@ function analyticsChartTemplates() {
|
|
|
5832
5756
|
limit: 10,
|
|
5833
5757
|
},
|
|
5834
5758
|
},
|
|
5759
|
+
{
|
|
5760
|
+
id: 'hourly_dimension_breakdown_24h',
|
|
5761
|
+
title: 'Hourly dimension breakdown (24h)',
|
|
5762
|
+
visualization: 'table',
|
|
5763
|
+
purpose: 'Compare one low-cardinality promoted dimension by hour over the last 24 hours.',
|
|
5764
|
+
usableAsIs: false,
|
|
5765
|
+
placeholders: {
|
|
5766
|
+
eventName: '<event_name>',
|
|
5767
|
+
property: '<queryable_dimension_property>',
|
|
5768
|
+
},
|
|
5769
|
+
adaptWith: [
|
|
5770
|
+
'Replace both placeholders with one observed custom event and one active dimension property for that event.',
|
|
5771
|
+
'Use only non-sensitive categorical dimensions with at most 16 observed values.',
|
|
5772
|
+
'Keep the 24h range, hour-first grouping order, table visualization, custom_event_count metric, and 400-row limit unchanged.',
|
|
5773
|
+
],
|
|
5774
|
+
spec: {
|
|
5775
|
+
version: 1,
|
|
5776
|
+
dataset: 'events',
|
|
5777
|
+
visualization: 'table',
|
|
5778
|
+
metric: { name: 'custom_event_count' },
|
|
5779
|
+
groupBy: [{ field: 'hour' }, { field: 'property:<queryable_dimension_property>' }],
|
|
5780
|
+
filters: [{ field: 'event_name', op: '=', value: '<event_name>' }],
|
|
5781
|
+
range: { preset: '24h' },
|
|
5782
|
+
sort: [{ field: 'hour', direction: 'asc' }],
|
|
5783
|
+
limit: 400,
|
|
5784
|
+
},
|
|
5785
|
+
},
|
|
5835
5786
|
{
|
|
5836
5787
|
id: 'experiment_readout',
|
|
5837
5788
|
title: 'Experiment readout',
|
|
@@ -5852,10 +5803,7 @@ function analyticsChartTemplates() {
|
|
|
5852
5803
|
dataset: 'events',
|
|
5853
5804
|
visualization: 'table',
|
|
5854
5805
|
metric: { name: 'custom_event_count' },
|
|
5855
|
-
groupBy: [
|
|
5856
|
-
{ field: 'event_name' },
|
|
5857
|
-
{ field: 'property:experiment_variant' },
|
|
5858
|
-
],
|
|
5806
|
+
groupBy: [{ field: 'event_name' }, { field: 'property:experiment_variant' }],
|
|
5859
5807
|
filters: [
|
|
5860
5808
|
{
|
|
5861
5809
|
field: 'event_name',
|
|
@@ -7166,30 +7114,6 @@ function parseTenderArgs(args) {
|
|
|
7166
7114
|
}
|
|
7167
7115
|
throw new TenderCliUsageError(`Unknown connectors command: ${args[1]}`);
|
|
7168
7116
|
}
|
|
7169
|
-
if (args[0] === 'shopify') {
|
|
7170
|
-
if (args[1] === undefined || args[1] === '--help' || args[1] === '-h') {
|
|
7171
|
-
return { command: 'help', topic: 'shopify' };
|
|
7172
|
-
}
|
|
7173
|
-
if (args[1] === 'workspace') {
|
|
7174
|
-
if (args[2] === undefined || args[2] === '--help' || args[2] === '-h') {
|
|
7175
|
-
return { command: 'help', topic: 'shopify workspace' };
|
|
7176
|
-
}
|
|
7177
|
-
if (args[2] === 'connectors' && args[3] === 'list') {
|
|
7178
|
-
return parseShopifyWorkspaceConnectorsListArgs(args.slice(4));
|
|
7179
|
-
}
|
|
7180
|
-
if (args[2] === 'link-runtime') {
|
|
7181
|
-
return parseShopifyWorkspaceLinkRuntimeArgs(args.slice(3));
|
|
7182
|
-
}
|
|
7183
|
-
if (args[2] === 'validate-source') {
|
|
7184
|
-
return parseShopifyWorkspaceSourceArgs(args.slice(3), 'shopify workspace validate-source');
|
|
7185
|
-
}
|
|
7186
|
-
if (args[2] === 'deploy-source') {
|
|
7187
|
-
return parseShopifyWorkspaceSourceArgs(args.slice(3), 'shopify workspace deploy-source');
|
|
7188
|
-
}
|
|
7189
|
-
throw new TenderCliUsageError(`Unknown shopify workspace command: ${args[2]}`);
|
|
7190
|
-
}
|
|
7191
|
-
throw new TenderCliUsageError(`Unknown shopify command: ${args[1]}`);
|
|
7192
|
-
}
|
|
7193
7117
|
if (args[0] === 'apps') {
|
|
7194
7118
|
throw new TenderCliUsageError('Unknown command: apps. Use `tender app ...` for Tender App workflows.');
|
|
7195
7119
|
}
|
|
@@ -7255,7 +7179,8 @@ function parseTenderArgs(args) {
|
|
|
7255
7179
|
if (resourceArgs[1] === 'bindings' && (resourceArgs[2] === undefined || resourceArgs[2] === '--help' || resourceArgs[2] === '-h')) {
|
|
7256
7180
|
return { command: 'help', topic: 'app bindings outbound' };
|
|
7257
7181
|
}
|
|
7258
|
-
if (resourceArgs[1] === 'outbound-auth' &&
|
|
7182
|
+
if (resourceArgs[1] === 'outbound-auth' &&
|
|
7183
|
+
(resourceArgs[2] === undefined || resourceArgs[2] === '--help' || resourceArgs[2] === '-h')) {
|
|
7259
7184
|
return { command: 'help', topic: 'app outbound-auth' };
|
|
7260
7185
|
}
|
|
7261
7186
|
if (resourceArgs[1] === 'outbound-auth' && resourceArgs[2] === 'list') {
|
|
@@ -7865,12 +7790,7 @@ async function requestAppDbDashboardApi(input) {
|
|
|
7865
7790
|
(typeof payload?.reasonCode === 'string' && payload.reasonCode) ||
|
|
7866
7791
|
`App database dashboard request failed with HTTP ${response.status}.`;
|
|
7867
7792
|
const example = appDbDashboardCorrectedExample(input.artifactId, input.path);
|
|
7868
|
-
throw new TenderCliDbApiError([
|
|
7869
|
-
message,
|
|
7870
|
-
`Scope: artifact ${input.artifactId}, app-db-dashboards.`,
|
|
7871
|
-
`Reason: ${reasonCode}.`,
|
|
7872
|
-
`Example: ${example}.`,
|
|
7873
|
-
].join('\n'), reasonCode, {
|
|
7793
|
+
throw new TenderCliDbApiError([message, `Scope: artifact ${input.artifactId}, app-db-dashboards.`, `Reason: ${reasonCode}.`, `Example: ${example}.`].join('\n'), reasonCode, {
|
|
7874
7794
|
artifactId: input.artifactId,
|
|
7875
7795
|
surface: 'db/dashboards',
|
|
7876
7796
|
}, example);
|
|
@@ -7926,189 +7846,13 @@ async function requestConnectorsApi(input) {
|
|
|
7926
7846
|
const message = (typeof payload?.message === 'string' && payload.message) ||
|
|
7927
7847
|
(typeof payload?.reasonCode === 'string' && payload.reasonCode) ||
|
|
7928
7848
|
`Connector request failed with HTTP ${response.status}.`;
|
|
7929
|
-
const example =
|
|
7930
|
-
|
|
7931
|
-
|
|
7932
|
-
|
|
7849
|
+
const example = 'Example: tender connectors list --profile account --json.';
|
|
7850
|
+
throw new TenderCliConnectorsApiError([message, `Reason: ${reasonCode}.`, example].join('\n'), reasonCode, response.status, payload ?? null, example
|
|
7851
|
+
.replace(/^Example:\s*/i, '')
|
|
7852
|
+
.replace(/\.$/, ''));
|
|
7933
7853
|
}
|
|
7934
7854
|
return payload;
|
|
7935
7855
|
}
|
|
7936
|
-
function normalizeHostedWorkspaceBaseUrl(value) {
|
|
7937
|
-
const baseUrl = value?.trim().replace(/\/+$/, '') ?? '';
|
|
7938
|
-
if (!baseUrl) {
|
|
7939
|
-
return null;
|
|
7940
|
-
}
|
|
7941
|
-
try {
|
|
7942
|
-
const url = new URL(baseUrl);
|
|
7943
|
-
return url.protocol === 'https:' || url.protocol === 'http:' ? url.toString().replace(/\/+$/, '') : null;
|
|
7944
|
-
}
|
|
7945
|
-
catch {
|
|
7946
|
-
return null;
|
|
7947
|
-
}
|
|
7948
|
-
}
|
|
7949
|
-
function readHostedWorkspaceArtifactId(value) {
|
|
7950
|
-
const artifactId = value?.trim() ?? '';
|
|
7951
|
-
return /^artifact_[A-Za-z0-9_-]+$/.test(artifactId) ? artifactId : null;
|
|
7952
|
-
}
|
|
7953
|
-
function isHostedWorkspaceInternalApiBaseUrl(baseUrl) {
|
|
7954
|
-
try {
|
|
7955
|
-
return new URL(baseUrl).hostname === 'tender-api.tender.test';
|
|
7956
|
-
}
|
|
7957
|
-
catch {
|
|
7958
|
-
return false;
|
|
7959
|
-
}
|
|
7960
|
-
}
|
|
7961
|
-
function hostedWorkspaceApiHeaders(input) {
|
|
7962
|
-
return {
|
|
7963
|
-
accept: 'application/json',
|
|
7964
|
-
...(input.jsonBody ? { 'content-type': 'application/json' } : {}),
|
|
7965
|
-
};
|
|
7966
|
-
}
|
|
7967
|
-
function readHostedWorkspaceConnectorId(value) {
|
|
7968
|
-
const connectorId = value?.trim() ?? '';
|
|
7969
|
-
return /^conn_[A-Za-z0-9_-]+$/.test(connectorId) ? connectorId : null;
|
|
7970
|
-
}
|
|
7971
|
-
function errorCodeMatches(error, code) {
|
|
7972
|
-
return Boolean(error &&
|
|
7973
|
-
typeof error === 'object' &&
|
|
7974
|
-
'code' in error &&
|
|
7975
|
-
error.code === code);
|
|
7976
|
-
}
|
|
7977
|
-
function hostedWorkspaceSelectionFilePath(runtime) {
|
|
7978
|
-
const env = runtime.env ?? process.env;
|
|
7979
|
-
const explicitPath = env.TENDER_WORKSPACE_STATE_FILE?.trim();
|
|
7980
|
-
if (explicitPath) {
|
|
7981
|
-
return path.resolve(explicitPath);
|
|
7982
|
-
}
|
|
7983
|
-
const runtimeProjectDir = env.TENDER_RUNTIME_PROJECT_DIR?.trim();
|
|
7984
|
-
if (runtimeProjectDir && path.isAbsolute(runtimeProjectDir)) {
|
|
7985
|
-
return path.join(path.dirname(runtimeProjectDir), '.tender', 'shopify-workspace.json');
|
|
7986
|
-
}
|
|
7987
|
-
const cwd = process.cwd();
|
|
7988
|
-
if (cwd === '/workspace' || cwd.startsWith('/workspace/')) {
|
|
7989
|
-
return '/workspace/.tender/shopify-workspace.json';
|
|
7990
|
-
}
|
|
7991
|
-
return null;
|
|
7992
|
-
}
|
|
7993
|
-
async function writeHostedWorkspaceConnectorSelection(input, runtime) {
|
|
7994
|
-
const filePath = hostedWorkspaceSelectionFilePath(runtime);
|
|
7995
|
-
if (!filePath) {
|
|
7996
|
-
return;
|
|
7997
|
-
}
|
|
7998
|
-
await mkdir(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
7999
|
-
await writeFile(filePath, `${JSON.stringify({
|
|
8000
|
-
version: 1,
|
|
8001
|
-
runtimeArtifactId: input.runtimeArtifactId,
|
|
8002
|
-
connectorId: input.connectorId,
|
|
8003
|
-
selectedAt: Date.now(),
|
|
8004
|
-
}, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
|
|
8005
|
-
}
|
|
8006
|
-
async function readHostedWorkspaceConnectorSelection(runtimeArtifactId, runtime) {
|
|
8007
|
-
const env = runtime.env ?? process.env;
|
|
8008
|
-
const envConnectorId = readHostedWorkspaceConnectorId(env.TENDER_SHOPIFY_CONNECTOR_ID);
|
|
8009
|
-
if (envConnectorId) {
|
|
8010
|
-
return envConnectorId;
|
|
8011
|
-
}
|
|
8012
|
-
const filePath = hostedWorkspaceSelectionFilePath(runtime);
|
|
8013
|
-
if (!filePath) {
|
|
8014
|
-
return null;
|
|
8015
|
-
}
|
|
8016
|
-
let content;
|
|
8017
|
-
try {
|
|
8018
|
-
content = await readFile(filePath, 'utf8');
|
|
8019
|
-
}
|
|
8020
|
-
catch (error) {
|
|
8021
|
-
if (errorCodeMatches(error, 'ENOENT')) {
|
|
8022
|
-
return null;
|
|
8023
|
-
}
|
|
8024
|
-
throw error;
|
|
8025
|
-
}
|
|
8026
|
-
let parsed;
|
|
8027
|
-
try {
|
|
8028
|
-
parsed = JSON.parse(content);
|
|
8029
|
-
}
|
|
8030
|
-
catch {
|
|
8031
|
-
return null;
|
|
8032
|
-
}
|
|
8033
|
-
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
8034
|
-
return null;
|
|
8035
|
-
}
|
|
8036
|
-
const record = parsed;
|
|
8037
|
-
if (record.runtimeArtifactId !== runtimeArtifactId) {
|
|
8038
|
-
return null;
|
|
8039
|
-
}
|
|
8040
|
-
return readHostedWorkspaceConnectorId(typeof record.connectorId === 'string' ? record.connectorId : undefined);
|
|
8041
|
-
}
|
|
8042
|
-
function shopifyWorkspaceCommandApiPath(command) {
|
|
8043
|
-
return command === 'shopify workspace deploy-source' ? 'deploy' : 'validate';
|
|
8044
|
-
}
|
|
8045
|
-
async function requestShopifyWorkspaceSourceCommand(input) {
|
|
8046
|
-
const url = `${input.baseUrl}/api/account/connectors/${encodeURIComponent(input.connectorId)}/source/${shopifyWorkspaceCommandApiPath(input.command)}`;
|
|
8047
|
-
const response = await input.fetcher(url, {
|
|
8048
|
-
method: 'POST',
|
|
8049
|
-
headers: hostedWorkspaceApiHeaders({
|
|
8050
|
-
jsonBody: true,
|
|
8051
|
-
}),
|
|
8052
|
-
body: JSON.stringify({
|
|
8053
|
-
sourceArtifactId: input.sourceArtifactId,
|
|
8054
|
-
runtimeArtifactId: input.runtimeArtifactId,
|
|
8055
|
-
}),
|
|
8056
|
-
});
|
|
8057
|
-
const payload = response.status === 204 ? { ok: true } : await parseJsonResponse(response);
|
|
8058
|
-
if (payload) {
|
|
8059
|
-
return { status: response.status, payload };
|
|
8060
|
-
}
|
|
8061
|
-
return {
|
|
8062
|
-
status: response.status,
|
|
8063
|
-
payload: {
|
|
8064
|
-
ok: false,
|
|
8065
|
-
reasonCode: `http_${response.status}`,
|
|
8066
|
-
message: `Shopify workspace request failed with HTTP ${response.status}.`,
|
|
8067
|
-
},
|
|
8068
|
-
};
|
|
8069
|
-
}
|
|
8070
|
-
async function requestShopifyWorkspaceConnectorsList(input) {
|
|
8071
|
-
const params = new URLSearchParams({ runtimeArtifactId: input.runtimeArtifactId });
|
|
8072
|
-
const url = `${input.baseUrl}/api/account/connectors?${params.toString()}`;
|
|
8073
|
-
const response = await input.fetcher(url, {
|
|
8074
|
-
method: 'GET',
|
|
8075
|
-
headers: hostedWorkspaceApiHeaders({}),
|
|
8076
|
-
});
|
|
8077
|
-
const payload = response.status === 204 ? { ok: true } : await parseJsonResponse(response);
|
|
8078
|
-
if (payload) {
|
|
8079
|
-
return { status: response.status, payload };
|
|
8080
|
-
}
|
|
8081
|
-
return {
|
|
8082
|
-
status: response.status,
|
|
8083
|
-
payload: {
|
|
8084
|
-
ok: false,
|
|
8085
|
-
reasonCode: `http_${response.status}`,
|
|
8086
|
-
message: `Shopify workspace connectors request failed with HTTP ${response.status}.`,
|
|
8087
|
-
},
|
|
8088
|
-
};
|
|
8089
|
-
}
|
|
8090
|
-
async function requestShopifyWorkspaceLinkRuntime(input) {
|
|
8091
|
-
const url = `${input.baseUrl}/api/account/connectors/${encodeURIComponent(input.connectorId)}/runtime-links`;
|
|
8092
|
-
const response = await input.fetcher(url, {
|
|
8093
|
-
method: 'POST',
|
|
8094
|
-
headers: hostedWorkspaceApiHeaders({
|
|
8095
|
-
jsonBody: true,
|
|
8096
|
-
}),
|
|
8097
|
-
body: JSON.stringify({ runtimeArtifactId: input.runtimeArtifactId }),
|
|
8098
|
-
});
|
|
8099
|
-
const payload = response.status === 204 ? { ok: true } : await parseJsonResponse(response);
|
|
8100
|
-
if (payload) {
|
|
8101
|
-
return { status: response.status, payload };
|
|
8102
|
-
}
|
|
8103
|
-
return {
|
|
8104
|
-
status: response.status,
|
|
8105
|
-
payload: {
|
|
8106
|
-
ok: false,
|
|
8107
|
-
reasonCode: `http_${response.status}`,
|
|
8108
|
-
message: `Shopify workspace link request failed with HTTP ${response.status}.`,
|
|
8109
|
-
},
|
|
8110
|
-
};
|
|
8111
|
-
}
|
|
8112
7856
|
function dbCorrectedExample(artifactId, dbPath) {
|
|
8113
7857
|
if (dbPath === 'capabilities') {
|
|
8114
7858
|
return `tender app db capabilities ${artifactId} --json`;
|
|
@@ -8223,26 +7967,29 @@ function readAnalyticsSpecMetricName(spec) {
|
|
|
8223
7967
|
}
|
|
8224
7968
|
return readCliStringField(spec.metric, 'name');
|
|
8225
7969
|
}
|
|
8226
|
-
function
|
|
7970
|
+
function readAnalyticsSpecGroupFields(spec) {
|
|
8227
7971
|
if (!isCliJsonRecord(spec) || !Array.isArray(spec.groupBy)) {
|
|
8228
|
-
return
|
|
7972
|
+
return [];
|
|
8229
7973
|
}
|
|
8230
|
-
|
|
8231
|
-
|
|
7974
|
+
return spec.groupBy
|
|
7975
|
+
.filter(isCliJsonRecord)
|
|
7976
|
+
.map((group) => readCliStringField(group, 'field'))
|
|
7977
|
+
.filter((field) => field !== null);
|
|
8232
7978
|
}
|
|
8233
7979
|
function readRequiredAnalyticsPropertiesFromSpec(spec) {
|
|
8234
7980
|
const eventName = readAnalyticsSpecEventNameFilter(spec);
|
|
8235
7981
|
const requirements = [];
|
|
8236
|
-
const groupField
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
7982
|
+
for (const groupField of readAnalyticsSpecGroupFields(spec)) {
|
|
7983
|
+
if (groupField.startsWith('property:')) {
|
|
7984
|
+
if (!eventName) {
|
|
7985
|
+
throw new TenderCliUsageError('Property group-by chart specs require an event_name = filter so the CLI can make the property available to dashboard queries.');
|
|
7986
|
+
}
|
|
7987
|
+
requirements.push({
|
|
7988
|
+
eventName,
|
|
7989
|
+
propertyKey: groupField.slice('property:'.length),
|
|
7990
|
+
propertyType: 'dimension',
|
|
7991
|
+
});
|
|
8240
7992
|
}
|
|
8241
|
-
requirements.push({
|
|
8242
|
-
eventName,
|
|
8243
|
-
propertyKey: groupField.slice('property:'.length),
|
|
8244
|
-
propertyType: 'dimension',
|
|
8245
|
-
});
|
|
8246
7993
|
}
|
|
8247
7994
|
const metricName = readAnalyticsSpecMetricName(spec);
|
|
8248
7995
|
if (metricName?.startsWith('sum:') || metricName?.startsWith('avg:')) {
|
|
@@ -8413,7 +8160,16 @@ function csvCell(value) {
|
|
|
8413
8160
|
return text;
|
|
8414
8161
|
}
|
|
8415
8162
|
function rowsToCsv(rows) {
|
|
8416
|
-
const records = rows
|
|
8163
|
+
const records = rows
|
|
8164
|
+
.filter((row) => Boolean(row) && typeof row === 'object' && !Array.isArray(row))
|
|
8165
|
+
.map((row) => {
|
|
8166
|
+
const dimensions = row.dimensions;
|
|
8167
|
+
if (!dimensions || typeof dimensions !== 'object' || Array.isArray(dimensions)) {
|
|
8168
|
+
return row;
|
|
8169
|
+
}
|
|
8170
|
+
const { dimensions: _dimensions, ...rest } = row;
|
|
8171
|
+
return { ...dimensions, ...rest };
|
|
8172
|
+
});
|
|
8417
8173
|
const headers = Array.from(records.reduce((set, row) => {
|
|
8418
8174
|
for (const key of Object.keys(row)) {
|
|
8419
8175
|
set.add(key);
|
|
@@ -9039,11 +8795,13 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
9039
8795
|
const fetcher = runtime.fetcher ?? fetch;
|
|
9040
8796
|
const sleeper = runtime.sleeper ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
9041
8797
|
const now = runtime.now ?? Date.now;
|
|
8798
|
+
const agentAutoPoll = command.json && env.TENDER_AGENT_DEVICE_AUTH_AUTO_POLL === '1';
|
|
9042
8799
|
const config = await readConfig(env);
|
|
9043
8800
|
const existingProfile = config.profiles?.[command.saveProfile] ?? null;
|
|
9044
8801
|
const baseUrl = command.baseUrl ?? existingProfile?.baseUrl ?? readDefaultBaseUrl(env);
|
|
9045
|
-
const
|
|
9046
|
-
const
|
|
8802
|
+
const defaultShopifyApprovalContext = readDefaultShopifyApprovalContext(env);
|
|
8803
|
+
const shopifyAppUrl = command.shopifyAppUrl ?? existingProfile?.shopifyAppUrl ?? defaultShopifyApprovalContext?.shopifyAppUrl ?? null;
|
|
8804
|
+
const shopifyStore = command.shopifyStore ?? existingProfile?.shopifyStore ?? defaultShopifyApprovalContext?.shopifyStore ?? null;
|
|
9047
8805
|
const deviceResponse = await fetcher(`${baseUrl}/oauth/device/code`, {
|
|
9048
8806
|
method: 'POST',
|
|
9049
8807
|
headers: {
|
|
@@ -9116,7 +8874,7 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
9116
8874
|
configPath: readConfigPath(env),
|
|
9117
8875
|
nextCommand: buildAuthStatusNextCommand(command.saveProfile),
|
|
9118
8876
|
};
|
|
9119
|
-
if (command.noPoll) {
|
|
8877
|
+
if (command.noPoll && !agentAutoPoll) {
|
|
9120
8878
|
if (command.json) {
|
|
9121
8879
|
io.stdout(JSON.stringify(pendingResult, null, 2));
|
|
9122
8880
|
}
|
|
@@ -9129,10 +8887,7 @@ async function runAuthLogin(command, io, runtime) {
|
|
|
9129
8887
|
io.stderr(JSON.stringify(pendingResult, null, 2));
|
|
9130
8888
|
}
|
|
9131
8889
|
else {
|
|
9132
|
-
io.stdout([
|
|
9133
|
-
`Open this URL to approve Tender CLI access: ${verificationUriComplete}`,
|
|
9134
|
-
`User code: ${devicePayload.user_code}`,
|
|
9135
|
-
].join('\n'));
|
|
8890
|
+
io.stdout([`Open this URL to approve Tender CLI access: ${verificationUriComplete}`, `User code: ${devicePayload.user_code}`].join('\n'));
|
|
9136
8891
|
}
|
|
9137
8892
|
let intervalSeconds = Math.max(1, devicePayload.interval ?? 5);
|
|
9138
8893
|
let attemptsRemaining = Math.max(1, Math.ceil(command.pollTimeoutSeconds / intervalSeconds));
|
|
@@ -9829,12 +9584,7 @@ async function resolveGitCommitArgument(input) {
|
|
|
9829
9584
|
return commitSha.toLowerCase();
|
|
9830
9585
|
}
|
|
9831
9586
|
const detail = result.stderr.trim() || result.stdout.trim();
|
|
9832
|
-
throw new TenderCliUsageError([
|
|
9833
|
-
`--commit must be a 40-character commit SHA or a Git ref resolvable in this checkout: ${value}`,
|
|
9834
|
-
detail,
|
|
9835
|
-
]
|
|
9836
|
-
.filter(Boolean)
|
|
9837
|
-
.join('\n'));
|
|
9587
|
+
throw new TenderCliUsageError([`--commit must be a 40-character commit SHA or a Git ref resolvable in this checkout: ${value}`, detail].filter(Boolean).join('\n'));
|
|
9838
9588
|
}
|
|
9839
9589
|
function readCommandCommitSha(command) {
|
|
9840
9590
|
const commitSha = 'commitSha' in command ? command.commitSha : null;
|
|
@@ -10805,9 +10555,7 @@ async function postLifecycle(input) {
|
|
|
10805
10555
|
},
|
|
10806
10556
|
runtime: input.runtime,
|
|
10807
10557
|
});
|
|
10808
|
-
const memorySync = input.operation === 'publish'
|
|
10809
|
-
? await flushAutomaticMemory({ repoPath, remoteName: 'tender', runtime: input.runtime })
|
|
10810
|
-
: null;
|
|
10558
|
+
const memorySync = input.operation === 'publish' ? await flushAutomaticMemory({ repoPath, remoteName: 'tender', runtime: input.runtime }) : null;
|
|
10811
10559
|
const resultWithMemory = {
|
|
10812
10560
|
...result,
|
|
10813
10561
|
memory,
|
|
@@ -11134,7 +10882,7 @@ async function runOutboundAuthApprove(command, io, runtime) {
|
|
|
11134
10882
|
});
|
|
11135
10883
|
const fetcher = runtime.fetcher ?? fetch;
|
|
11136
10884
|
const grants = command.approveAll
|
|
11137
|
-
? (await requestOutboundAuthList({ command, credentials, fetcher })).grants?.filter((grant) => !grant.approved) ?? []
|
|
10885
|
+
? ((await requestOutboundAuthList({ command, credentials, fetcher })).grants?.filter((grant) => !grant.approved) ?? [])
|
|
11138
10886
|
: [readExplicitOutboundAuthGrant(command)];
|
|
11139
10887
|
const approved = [];
|
|
11140
10888
|
for (const grant of grants) {
|
|
@@ -11154,11 +10902,7 @@ async function runOutboundAuthApprove(command, io, runtime) {
|
|
|
11154
10902
|
io.stdout(JSON.stringify(result, null, 2));
|
|
11155
10903
|
}
|
|
11156
10904
|
else {
|
|
11157
|
-
io.stdout([
|
|
11158
|
-
`app_id: ${command.artifactId}`,
|
|
11159
|
-
`approved: ${approved.length}`,
|
|
11160
|
-
...approved.map((grant) => formatOutboundAuthGrant(grant)),
|
|
11161
|
-
].join('\n'));
|
|
10905
|
+
io.stdout([`app_id: ${command.artifactId}`, `approved: ${approved.length}`, ...approved.map((grant) => formatOutboundAuthGrant(grant))].join('\n'));
|
|
11162
10906
|
}
|
|
11163
10907
|
return 0;
|
|
11164
10908
|
}
|
|
@@ -11577,7 +11321,9 @@ async function runMemoryBrief(command, io, runtime) {
|
|
|
11577
11321
|
const unresolved = entries
|
|
11578
11322
|
.filter((entry) => entry.status === 'open' || entry.kind === 'failure' || entry.kind === 'open_question' || entry.kind === 'support_handoff')
|
|
11579
11323
|
.slice(-command.limit);
|
|
11580
|
-
const briefEntries = latestSummary
|
|
11324
|
+
const briefEntries = latestSummary
|
|
11325
|
+
? [latestSummary, ...unresolved.filter((entry) => entry.entryId !== latestSummary.entryId)]
|
|
11326
|
+
: unresolved;
|
|
11581
11327
|
const result = {
|
|
11582
11328
|
ok: true,
|
|
11583
11329
|
command: command.command,
|
|
@@ -11613,11 +11359,7 @@ async function runMemoryFlush(command, io, runtime) {
|
|
|
11613
11359
|
io.stdout(JSON.stringify(result, null, 2));
|
|
11614
11360
|
}
|
|
11615
11361
|
else {
|
|
11616
|
-
io.stdout([
|
|
11617
|
-
`memory_sync: ${sync.status}`,
|
|
11618
|
-
sync.error ? `error: ${sync.error}` : null,
|
|
11619
|
-
result.next ? `next: ${result.next}` : null,
|
|
11620
|
-
]
|
|
11362
|
+
io.stdout([`memory_sync: ${sync.status}`, sync.error ? `error: ${sync.error}` : null, result.next ? `next: ${result.next}` : null]
|
|
11621
11363
|
.filter((line) => Boolean(line))
|
|
11622
11364
|
.join('\n'));
|
|
11623
11365
|
}
|
|
@@ -11835,7 +11577,6 @@ async function runConnectorsList(command, io, runtime) {
|
|
|
11835
11577
|
baseUrl: credentials.baseUrl,
|
|
11836
11578
|
token: credentials.token,
|
|
11837
11579
|
fetcher: runtime.fetcher ?? fetch,
|
|
11838
|
-
hostedWorkspaceHint: Boolean((runtime.env ?? process.env).TENDER_RUNTIME_ARTIFACT_ID),
|
|
11839
11580
|
});
|
|
11840
11581
|
const connectors = readConnectorArray(payload);
|
|
11841
11582
|
printConnectorsPayload(command, io, payload, connectors.length > 0
|
|
@@ -11888,10 +11629,7 @@ async function runConnectorsShopifySetupUrl(command, io, runtime) {
|
|
|
11888
11629
|
token: credentials.token,
|
|
11889
11630
|
fetcher: runtime.fetcher ?? fetch,
|
|
11890
11631
|
});
|
|
11891
|
-
printConnectorsPayload(command, io, payload, [
|
|
11892
|
-
`connector_id: ${command.connectorId}`,
|
|
11893
|
-
`setup_url: ${String(payload.setupUrl ?? '')}`,
|
|
11894
|
-
]);
|
|
11632
|
+
printConnectorsPayload(command, io, payload, [`connector_id: ${command.connectorId}`, `setup_url: ${String(payload.setupUrl ?? '')}`]);
|
|
11895
11633
|
return 0;
|
|
11896
11634
|
}
|
|
11897
11635
|
function readClientSecret(command, runtime) {
|
|
@@ -12128,7 +11866,10 @@ merchant-editable settings.
|
|
|
12128
11866
|
`;
|
|
12129
11867
|
return new Map([
|
|
12130
11868
|
['extensions/tender-product-survey/README.md', readme],
|
|
12131
|
-
[
|
|
11869
|
+
[
|
|
11870
|
+
'extensions/tender-product-survey/package.json',
|
|
11871
|
+
'{\n "name": "tender-product-survey",\n "private": true,\n "version": "0.0.0"\n}\n',
|
|
11872
|
+
],
|
|
12132
11873
|
['extensions/tender-product-survey/shopify.extension.toml', extensionToml],
|
|
12133
11874
|
['extensions/tender-product-survey/blocks/product_survey.liquid', blockLiquid],
|
|
12134
11875
|
['extensions/tender-product-survey/locales/en.default.json', '{}\n'],
|
|
@@ -12220,7 +11961,7 @@ spawned Shopify CLI child process.
|
|
|
12220
11961
|
## Validate
|
|
12221
11962
|
|
|
12222
11963
|
\`\`\`sh
|
|
12223
|
-
|
|
11964
|
+
npm exec --yes --package @shopify/cli@latest -- shopify app config validate --path . --json
|
|
12224
11965
|
\`\`\`
|
|
12225
11966
|
|
|
12226
11967
|
## Deploy
|
|
@@ -12229,16 +11970,10 @@ npx --yes @shopify/cli@latest app config validate --path . --json
|
|
|
12229
11970
|
tender connectors shopify source deploy ${input.connectorId} --dir . --confirm deploy --profile account --json
|
|
12230
11971
|
\`\`\`
|
|
12231
11972
|
|
|
12232
|
-
Inside
|
|
12233
|
-
|
|
12234
|
-
|
|
12235
|
-
|
|
12236
|
-
\`shopify app config validate\` and \`shopify app deploy\` are allowed. Do not run
|
|
12237
|
-
\`tender connectors shopify source validate\` or
|
|
12238
|
-
\`tender connectors shopify source deploy\` from hosted workspaces; those
|
|
12239
|
-
commands require an account-scoped Tender CLI token. Never ask for Shopify App
|
|
12240
|
-
Automation Tokens, client secrets, Tender tokens, or provider keys in the
|
|
12241
|
-
terminal.
|
|
11973
|
+
Inside Shopify, request source changes in the Custom app editor. Tender
|
|
11974
|
+
validates the pushed commit, and the explicit Deploy source action uses the
|
|
11975
|
+
connector's stored App Automation Token server-side. Never ask for Shopify App
|
|
11976
|
+
Automation Tokens, client secrets, Tender tokens, or provider keys in chat.
|
|
12242
11977
|
`;
|
|
12243
11978
|
const files = new Map([
|
|
12244
11979
|
['.gitignore', '.shopify/\nnode_modules/\n.env\n.env.*\n'],
|
|
@@ -12481,22 +12216,10 @@ async function commitAndPushSourceFiles(input) {
|
|
|
12481
12216
|
};
|
|
12482
12217
|
}
|
|
12483
12218
|
function shopifyValidateArgs(input) {
|
|
12484
|
-
return [
|
|
12485
|
-
'--yes',
|
|
12486
|
-
'@shopify/cli@latest',
|
|
12487
|
-
'app',
|
|
12488
|
-
'config',
|
|
12489
|
-
'validate',
|
|
12490
|
-
'--path',
|
|
12491
|
-
input.dir,
|
|
12492
|
-
'--json',
|
|
12493
|
-
...(input.clientId ? ['--client-id', input.clientId] : []),
|
|
12494
|
-
];
|
|
12219
|
+
return ['app', 'config', 'validate', '--path', input.dir, '--json', ...(input.clientId ? ['--client-id', input.clientId] : [])];
|
|
12495
12220
|
}
|
|
12496
12221
|
function shopifyDeployArgs(input) {
|
|
12497
12222
|
return [
|
|
12498
|
-
'--yes',
|
|
12499
|
-
'@shopify/cli@latest',
|
|
12500
12223
|
'app',
|
|
12501
12224
|
'deploy',
|
|
12502
12225
|
'--path',
|
|
@@ -12508,9 +12231,50 @@ function shopifyDeployArgs(input) {
|
|
|
12508
12231
|
...(input.sourceControlUrl ? ['--source-control-url', input.sourceControlUrl] : []),
|
|
12509
12232
|
];
|
|
12510
12233
|
}
|
|
12234
|
+
function shopifyCliCommand(args) {
|
|
12235
|
+
return {
|
|
12236
|
+
command: 'npm',
|
|
12237
|
+
args: ['exec', '--yes', '--package', SHOPIFY_CLI_PACKAGE, '--', SHOPIFY_CLI_BIN, ...args],
|
|
12238
|
+
};
|
|
12239
|
+
}
|
|
12240
|
+
function shopifyCliCommandLine(args) {
|
|
12241
|
+
const invocation = shopifyCliCommand(args);
|
|
12242
|
+
return commandLine(invocation.command, invocation.args);
|
|
12243
|
+
}
|
|
12511
12244
|
function commandLine(command, args) {
|
|
12512
12245
|
return [command, ...args.map(shellQuote)].join(' ');
|
|
12513
12246
|
}
|
|
12247
|
+
function shouldDropNestedNpmEnv(key) {
|
|
12248
|
+
const normalized = key.toLowerCase();
|
|
12249
|
+
return (normalized === 'init_cwd' ||
|
|
12250
|
+
normalized === 'npm_command' ||
|
|
12251
|
+
normalized === 'npm_config_argv' ||
|
|
12252
|
+
normalized === 'npm_config_call' ||
|
|
12253
|
+
normalized === 'npm_config_package' ||
|
|
12254
|
+
normalized === 'npm_execpath' ||
|
|
12255
|
+
normalized === 'npm_node_execpath' ||
|
|
12256
|
+
normalized.startsWith('npm_lifecycle_') ||
|
|
12257
|
+
normalized.startsWith('npm_package_'));
|
|
12258
|
+
}
|
|
12259
|
+
function sanitizeNestedNpmEnvironment(env) {
|
|
12260
|
+
const sanitized = {};
|
|
12261
|
+
for (const [key, value] of Object.entries(env)) {
|
|
12262
|
+
if (shouldDropNestedNpmEnv(key)) {
|
|
12263
|
+
continue;
|
|
12264
|
+
}
|
|
12265
|
+
sanitized[key] = value;
|
|
12266
|
+
}
|
|
12267
|
+
return sanitized;
|
|
12268
|
+
}
|
|
12269
|
+
function shopifyCliChildEnv(runtime, overrides = {}) {
|
|
12270
|
+
return {
|
|
12271
|
+
...sanitizeNestedNpmEnvironment({
|
|
12272
|
+
...process.env,
|
|
12273
|
+
...(runtime.env ?? {}),
|
|
12274
|
+
}),
|
|
12275
|
+
...overrides,
|
|
12276
|
+
};
|
|
12277
|
+
}
|
|
12514
12278
|
function redactSensitiveText(value, secrets = []) {
|
|
12515
12279
|
let redacted = value;
|
|
12516
12280
|
for (const secret of secrets) {
|
|
@@ -12524,11 +12288,12 @@ function redactSensitiveText(value, secrets = []) {
|
|
|
12524
12288
|
.replace(/\b(authorization\s*[:=]\s*(?:Bearer\s+)?)([^\s'"]+)/gi, '$1[REDACTED]')
|
|
12525
12289
|
.replace(/\b(x-shopify-access-token\s*[:=]\s*)([^\s'"]+)/gi, '$1[REDACTED]');
|
|
12526
12290
|
}
|
|
12527
|
-
async function
|
|
12291
|
+
async function runShopifyCliCommand(input) {
|
|
12528
12292
|
const runner = input.runtime.commandRunner ?? defaultCommandRunner;
|
|
12529
|
-
const
|
|
12293
|
+
const invocation = shopifyCliCommand(input.args);
|
|
12294
|
+
const result = await runner(invocation.command, invocation.args, {
|
|
12530
12295
|
cwd: input.cwd,
|
|
12531
|
-
|
|
12296
|
+
env: shopifyCliChildEnv(input.runtime, input.env),
|
|
12532
12297
|
});
|
|
12533
12298
|
const redactedResult = {
|
|
12534
12299
|
...result,
|
|
@@ -12536,12 +12301,13 @@ async function runNpxCommand(input) {
|
|
|
12536
12301
|
stderr: redactSensitiveText(result.stderr, input.redactions),
|
|
12537
12302
|
};
|
|
12538
12303
|
if (redactedResult.exitCode !== 0) {
|
|
12539
|
-
throw new
|
|
12540
|
-
|
|
12541
|
-
|
|
12542
|
-
|
|
12543
|
-
.
|
|
12544
|
-
.
|
|
12304
|
+
throw new TenderCliChildProcessError({
|
|
12305
|
+
command: invocation.command,
|
|
12306
|
+
args: invocation.args,
|
|
12307
|
+
exitCode: redactedResult.exitCode,
|
|
12308
|
+
stdout: redactedResult.stdout,
|
|
12309
|
+
stderr: redactedResult.stderr,
|
|
12310
|
+
});
|
|
12545
12311
|
}
|
|
12546
12312
|
return redactedResult;
|
|
12547
12313
|
}
|
|
@@ -12570,28 +12336,26 @@ function localShopifyDeployToken(runtime) {
|
|
|
12570
12336
|
const token = env.SHOPIFY_APP_AUTOMATION_TOKEN;
|
|
12571
12337
|
return token?.trim() ? token.trim() : null;
|
|
12572
12338
|
}
|
|
12573
|
-
function childShopifyDeployEnv(
|
|
12339
|
+
function childShopifyDeployEnv(_runtime, token) {
|
|
12574
12340
|
return {
|
|
12575
|
-
...process.env,
|
|
12576
|
-
...(runtime.env ?? {}),
|
|
12577
12341
|
SHOPIFY_APP_AUTOMATION_TOKEN: token,
|
|
12578
12342
|
};
|
|
12579
12343
|
}
|
|
12580
12344
|
function connectorsProfileFlag(command) {
|
|
12581
12345
|
return command.profileName ? `--profile ${command.profileName}` : '--profile account';
|
|
12582
12346
|
}
|
|
12583
|
-
function
|
|
12347
|
+
function shopifyCliTokenRecoveryMessage(command, reasonCode) {
|
|
12584
12348
|
const profileFlag = connectorsProfileFlag(command);
|
|
12585
12349
|
return [
|
|
12586
12350
|
reasonCode === 'shopify_deploy_token_expired'
|
|
12587
12351
|
? 'The connector stored Shopify App Automation Token is expired.'
|
|
12588
|
-
: 'The connector does not have a stored Shopify App Automation Token for source
|
|
12352
|
+
: 'The connector does not have a stored Shopify App Automation Token for source validation and deployment.',
|
|
12589
12353
|
`Store a fresh token: printf "$SHOPIFY_APP_AUTOMATION_TOKEN" | tender connectors shopify app-automation-token set ${command.connectorId} --token-stdin ${profileFlag} --json`,
|
|
12590
12354
|
`Setup URL: tender connectors shopify setup-url ${command.connectorId} ${profileFlag} --json`,
|
|
12591
|
-
'Fallback: export SHOPIFY_APP_AUTOMATION_TOKEN locally before running source
|
|
12355
|
+
'Fallback: export SHOPIFY_APP_AUTOMATION_TOKEN locally before running source validation or deployment.',
|
|
12592
12356
|
].join('\n');
|
|
12593
12357
|
}
|
|
12594
|
-
async function
|
|
12358
|
+
async function resolveShopifyCliTokenEnvironment(input) {
|
|
12595
12359
|
const localToken = localShopifyDeployToken(input.runtime);
|
|
12596
12360
|
if (localToken) {
|
|
12597
12361
|
return {
|
|
@@ -12604,7 +12368,9 @@ async function resolveShopifyDeployTokenEnvironment(input) {
|
|
|
12604
12368
|
const handoff = await fetchShopifyDeployToken({
|
|
12605
12369
|
connectorId: input.command.connectorId,
|
|
12606
12370
|
sourceArtifactId: input.sourceArtifactId,
|
|
12607
|
-
version: input.command.
|
|
12371
|
+
version: input.command.command === 'connectors shopify source deploy'
|
|
12372
|
+
? input.command.version
|
|
12373
|
+
: null,
|
|
12608
12374
|
credentials: input.credentials,
|
|
12609
12375
|
fetcher: input.fetcher,
|
|
12610
12376
|
});
|
|
@@ -12617,7 +12383,7 @@ async function resolveShopifyDeployTokenEnvironment(input) {
|
|
|
12617
12383
|
catch (error) {
|
|
12618
12384
|
if (error instanceof TenderCliConnectorsApiError &&
|
|
12619
12385
|
(error.reasonCode === 'shopify_deploy_token_required' || error.reasonCode === 'shopify_deploy_token_expired')) {
|
|
12620
|
-
const recovery =
|
|
12386
|
+
const recovery = shopifyCliTokenRecoveryMessage(input.command, error.reasonCode);
|
|
12621
12387
|
throw new TenderCliGuidedUsageError(recovery, recovery);
|
|
12622
12388
|
}
|
|
12623
12389
|
throw error;
|
|
@@ -12682,8 +12448,15 @@ async function runConnectorsShopifySourceInit(command, io, runtime) {
|
|
|
12682
12448
|
sourceCheckout: checkout.sourceCheckout,
|
|
12683
12449
|
commit: gitCommit,
|
|
12684
12450
|
},
|
|
12685
|
-
validateCommand:
|
|
12686
|
-
deployCommand:
|
|
12451
|
+
validateCommand: shopifyCliCommandLine(shopifyValidateArgs({ dir, clientId: command.clientId })),
|
|
12452
|
+
deployCommand: shopifyCliCommandLine(shopifyDeployArgs({
|
|
12453
|
+
dir,
|
|
12454
|
+
clientId: command.clientId,
|
|
12455
|
+
version: 'connector-source',
|
|
12456
|
+
message: null,
|
|
12457
|
+
sourceControlUrl: null,
|
|
12458
|
+
allowDeletes: false,
|
|
12459
|
+
})),
|
|
12687
12460
|
};
|
|
12688
12461
|
printConnectorsPayload(command, io, result, [
|
|
12689
12462
|
`connector_id: ${command.connectorId}`,
|
|
@@ -12731,7 +12504,7 @@ async function runConnectorsShopifySourceCheckout(command, io, runtime) {
|
|
|
12731
12504
|
return 0;
|
|
12732
12505
|
}
|
|
12733
12506
|
async function runConnectorsShopifySourceValidate(command, io, runtime) {
|
|
12734
|
-
const { connector } = await requestConnector(command, runtime);
|
|
12507
|
+
const { credentials, connector } = await requestConnector(command, runtime);
|
|
12735
12508
|
const sourceArtifactId = readConnectorSourceArtifactId(connector);
|
|
12736
12509
|
if (!sourceArtifactId) {
|
|
12737
12510
|
throw new TenderCliUsageError('Connector does not have a source artifact yet. Run tender connectors shopify source init <connector-id> --dir ./shopify-app first.');
|
|
@@ -12745,11 +12518,46 @@ async function runConnectorsShopifySourceValidate(command, io, runtime) {
|
|
|
12745
12518
|
}
|
|
12746
12519
|
}
|
|
12747
12520
|
if (!command.execute) {
|
|
12748
|
-
printConnectorsPayload(command, io, {
|
|
12521
|
+
printConnectorsPayload(command, io, {
|
|
12522
|
+
ok: true,
|
|
12523
|
+
dryRun: true,
|
|
12524
|
+
command: command.command,
|
|
12525
|
+
connectorId: command.connectorId,
|
|
12526
|
+
sourceArtifactId,
|
|
12527
|
+
dir,
|
|
12528
|
+
shopifyCommand: shopifyCliCommandLine(args),
|
|
12529
|
+
}, [
|
|
12530
|
+
`connector_id: ${command.connectorId}`,
|
|
12531
|
+
`source_artifact_id: ${sourceArtifactId}`,
|
|
12532
|
+
`source_dir: ${dir}`,
|
|
12533
|
+
`shopify_command: ${shopifyCliCommandLine(args)}`,
|
|
12534
|
+
]);
|
|
12749
12535
|
return 0;
|
|
12750
12536
|
}
|
|
12751
|
-
const
|
|
12752
|
-
|
|
12537
|
+
const validationToken = await resolveShopifyCliTokenEnvironment({
|
|
12538
|
+
command,
|
|
12539
|
+
sourceArtifactId,
|
|
12540
|
+
credentials,
|
|
12541
|
+
runtime,
|
|
12542
|
+
fetcher: runtime.fetcher ?? fetch,
|
|
12543
|
+
});
|
|
12544
|
+
const result = await runShopifyCliCommand({
|
|
12545
|
+
args,
|
|
12546
|
+
cwd: dir,
|
|
12547
|
+
runtime,
|
|
12548
|
+
env: validationToken.env,
|
|
12549
|
+
redactions: validationToken.redactions,
|
|
12550
|
+
});
|
|
12551
|
+
printConnectorsPayload(command, io, {
|
|
12552
|
+
ok: true,
|
|
12553
|
+
command: command.command,
|
|
12554
|
+
connectorId: command.connectorId,
|
|
12555
|
+
sourceArtifactId,
|
|
12556
|
+
dir,
|
|
12557
|
+
exitCode: result.exitCode,
|
|
12558
|
+
stdout: result.stdout,
|
|
12559
|
+
stderr: result.stderr,
|
|
12560
|
+
}, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, 'validation: passed']);
|
|
12753
12561
|
return 0;
|
|
12754
12562
|
}
|
|
12755
12563
|
async function runConnectorsShopifySourceDeploy(command, io, runtime) {
|
|
@@ -12769,277 +12577,43 @@ async function runConnectorsShopifySourceDeploy(command, io, runtime) {
|
|
|
12769
12577
|
allowDeletes: command.allowDeletes,
|
|
12770
12578
|
});
|
|
12771
12579
|
if (command.confirm !== 'deploy') {
|
|
12772
|
-
printConnectorsPayload(command, io, {
|
|
12580
|
+
printConnectorsPayload(command, io, {
|
|
12581
|
+
ok: true,
|
|
12582
|
+
dryRun: true,
|
|
12583
|
+
command: command.command,
|
|
12584
|
+
connectorId: command.connectorId,
|
|
12585
|
+
sourceArtifactId,
|
|
12586
|
+
dir,
|
|
12587
|
+
shopifyCommand: shopifyCliCommandLine(args),
|
|
12588
|
+
confirmRequired: 'deploy',
|
|
12589
|
+
}, [
|
|
12590
|
+
`connector_id: ${command.connectorId}`,
|
|
12591
|
+
`source_artifact_id: ${sourceArtifactId}`,
|
|
12592
|
+
`source_dir: ${dir}`,
|
|
12593
|
+
`shopify_command: ${shopifyCliCommandLine(args)}`,
|
|
12594
|
+
'confirm_required: --confirm deploy',
|
|
12595
|
+
]);
|
|
12773
12596
|
return 0;
|
|
12774
12597
|
}
|
|
12775
|
-
const deployToken = await
|
|
12598
|
+
const deployToken = await resolveShopifyCliTokenEnvironment({
|
|
12776
12599
|
command,
|
|
12777
12600
|
sourceArtifactId,
|
|
12778
12601
|
credentials,
|
|
12779
12602
|
runtime,
|
|
12780
12603
|
fetcher,
|
|
12781
12604
|
});
|
|
12782
|
-
const result = await
|
|
12783
|
-
printConnectorsPayload(command, io, {
|
|
12784
|
-
return 0;
|
|
12785
|
-
}
|
|
12786
|
-
function shopifyWorkspaceResultKey(command) {
|
|
12787
|
-
return command === 'shopify workspace deploy-source' ? 'deployment' : 'validation';
|
|
12788
|
-
}
|
|
12789
|
-
function shopifyWorkspaceCommandSucceeded(command, payload) {
|
|
12790
|
-
if (payload.ok !== true) {
|
|
12791
|
-
return false;
|
|
12792
|
-
}
|
|
12793
|
-
const result = payload[shopifyWorkspaceResultKey(command.command)];
|
|
12794
|
-
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
12795
|
-
const ok = result.ok;
|
|
12796
|
-
return ok !== false;
|
|
12797
|
-
}
|
|
12798
|
-
return true;
|
|
12799
|
-
}
|
|
12800
|
-
function shopifyWorkspaceTextLines(command, payload) {
|
|
12801
|
-
const connector = payload.connector && typeof payload.connector === 'object' && !Array.isArray(payload.connector)
|
|
12802
|
-
? payload.connector
|
|
12803
|
-
: {};
|
|
12804
|
-
const source = payload.source && typeof payload.source === 'object' && !Array.isArray(payload.source)
|
|
12805
|
-
? payload.source
|
|
12806
|
-
: {};
|
|
12807
|
-
const result = payload[shopifyWorkspaceResultKey(command.command)];
|
|
12808
|
-
const resultRecord = result && typeof result === 'object' && !Array.isArray(result)
|
|
12809
|
-
? result
|
|
12810
|
-
: {};
|
|
12811
|
-
const succeeded = shopifyWorkspaceCommandSucceeded(command, payload);
|
|
12812
|
-
const verb = command.command === 'shopify workspace deploy-source' ? 'deploy' : 'validate';
|
|
12813
|
-
return [
|
|
12814
|
-
`${verb}: ${succeeded ? 'completed' : 'failed'}`,
|
|
12815
|
-
`connector_id: ${String(connector.connectorId ?? '')}`,
|
|
12816
|
-
`source_artifact_id: ${String(source.sourceArtifactId ?? '')}`,
|
|
12817
|
-
...(payload.reasonCode ? [`reason: ${String(payload.reasonCode)}`] : []),
|
|
12818
|
-
...(resultRecord.exitCode !== undefined ? [`exit_code: ${String(resultRecord.exitCode)}`] : []),
|
|
12819
|
-
...(resultRecord.stderr ? [`stderr: ${String(resultRecord.stderr).trim()}`] : []),
|
|
12820
|
-
].filter((line) => !line.endsWith(': '));
|
|
12821
|
-
}
|
|
12822
|
-
function readShopifyWorkspaceRuntimeIdentity(runtime) {
|
|
12823
|
-
const env = runtime.env ?? process.env;
|
|
12824
|
-
const baseUrl = normalizeHostedWorkspaceBaseUrl(env.TENDER_BASE_URL);
|
|
12825
|
-
if (!baseUrl) {
|
|
12826
|
-
throw new TenderCliUsageError('TENDER_BASE_URL is required. Open or reload the hosted Shopify workspace and run this command inside its terminal.');
|
|
12827
|
-
}
|
|
12828
|
-
if (!isHostedWorkspaceInternalApiBaseUrl(baseUrl)) {
|
|
12829
|
-
throw new TenderCliUsageError('TENDER_BASE_URL must be http://tender-api.tender.test inside a hosted Shopify workspace. Reload the workspace terminal and try again.');
|
|
12830
|
-
}
|
|
12831
|
-
const runtimeArtifactId = readHostedWorkspaceArtifactId(env.TENDER_RUNTIME_ARTIFACT_ID);
|
|
12832
|
-
if (!runtimeArtifactId) {
|
|
12833
|
-
throw new TenderCliUsageError('TENDER_RUNTIME_ARTIFACT_ID is required. This command only runs inside a hosted Shopify project workspace.');
|
|
12834
|
-
}
|
|
12835
|
-
return { baseUrl, runtimeArtifactId };
|
|
12836
|
-
}
|
|
12837
|
-
function readShopifyWorkspaceConnectorRecords(payload) {
|
|
12838
|
-
return Array.isArray(payload.connectors)
|
|
12839
|
-
? payload.connectors.filter((connector) => Boolean(connector) && typeof connector === 'object' && !Array.isArray(connector))
|
|
12840
|
-
: [];
|
|
12841
|
-
}
|
|
12842
|
-
function readShopifyWorkspaceConnectorSourceArtifactId(connector) {
|
|
12843
|
-
const sourceProject = connector.sourceProject && typeof connector.sourceProject === 'object' && !Array.isArray(connector.sourceProject)
|
|
12844
|
-
? connector.sourceProject
|
|
12845
|
-
: null;
|
|
12846
|
-
return readHostedWorkspaceArtifactId(typeof sourceProject?.sourceArtifactId === 'string' ? sourceProject.sourceArtifactId : undefined);
|
|
12847
|
-
}
|
|
12848
|
-
function resolveShopifyWorkspaceSourceCommandConnector(input) {
|
|
12849
|
-
const connectors = readShopifyWorkspaceConnectorRecords(input.payload);
|
|
12850
|
-
const connector = input.selectedConnectorId
|
|
12851
|
-
? connectors.find((candidate) => candidate.connectorId === input.selectedConnectorId) ?? null
|
|
12852
|
-
: (() => {
|
|
12853
|
-
const linked = connectors.filter((candidate) => candidate.linked === true);
|
|
12854
|
-
return linked.length === 1 ? linked[0] ?? null : null;
|
|
12855
|
-
})();
|
|
12856
|
-
if (!connector) {
|
|
12857
|
-
return {
|
|
12858
|
-
ok: false,
|
|
12859
|
-
payload: {
|
|
12860
|
-
ok: false,
|
|
12861
|
-
reasonCode: input.selectedConnectorId
|
|
12862
|
-
? 'shopify_workspace_selected_connector_not_found'
|
|
12863
|
-
: 'shopify_workspace_connector_selection_required',
|
|
12864
|
-
message: input.selectedConnectorId
|
|
12865
|
-
? 'The selected Shopify connector is no longer available to this workspace.'
|
|
12866
|
-
: 'Link this runtime to one Shopify connector before validating or deploying source.',
|
|
12867
|
-
},
|
|
12868
|
-
};
|
|
12869
|
-
}
|
|
12870
|
-
const connectorId = typeof connector.connectorId === 'string' ? connector.connectorId : null;
|
|
12871
|
-
const sourceArtifactId = readShopifyWorkspaceConnectorSourceArtifactId(connector);
|
|
12872
|
-
if (!connectorId || !sourceArtifactId) {
|
|
12873
|
-
return {
|
|
12874
|
-
ok: false,
|
|
12875
|
-
payload: {
|
|
12876
|
-
ok: false,
|
|
12877
|
-
reasonCode: 'shopify_source_project_required',
|
|
12878
|
-
message: 'The selected Shopify connector does not have a source project.',
|
|
12879
|
-
},
|
|
12880
|
-
};
|
|
12881
|
-
}
|
|
12882
|
-
return {
|
|
12605
|
+
const result = await runShopifyCliCommand({ args, cwd: dir, runtime, env: deployToken.env, redactions: deployToken.redactions });
|
|
12606
|
+
printConnectorsPayload(command, io, {
|
|
12883
12607
|
ok: true,
|
|
12884
|
-
connectorId,
|
|
12885
|
-
sourceArtifactId,
|
|
12886
|
-
};
|
|
12887
|
-
}
|
|
12888
|
-
async function runShopifyWorkspaceSourceCommand(command, io, runtime) {
|
|
12889
|
-
const { baseUrl, runtimeArtifactId } = readShopifyWorkspaceRuntimeIdentity(runtime);
|
|
12890
|
-
const selectedConnectorId = await readHostedWorkspaceConnectorSelection(runtimeArtifactId, runtime);
|
|
12891
|
-
const connectorsResult = await requestShopifyWorkspaceConnectorsList({
|
|
12892
|
-
baseUrl,
|
|
12893
|
-
runtimeArtifactId,
|
|
12894
|
-
fetcher: runtime.fetcher ?? fetch,
|
|
12895
|
-
});
|
|
12896
|
-
if (!shopifyWorkspaceRequestSucceeded(connectorsResult)) {
|
|
12897
|
-
const payload = {
|
|
12898
|
-
...connectorsResult.payload,
|
|
12899
|
-
command: command.command,
|
|
12900
|
-
runtimeArtifactId,
|
|
12901
|
-
...(selectedConnectorId ? { selectedConnectorId } : {}),
|
|
12902
|
-
};
|
|
12903
|
-
if (command.json) {
|
|
12904
|
-
io.stdout(JSON.stringify(payload, null, 2));
|
|
12905
|
-
}
|
|
12906
|
-
else {
|
|
12907
|
-
io.stdout(shopifyWorkspaceTextLines(command, payload).join('\n'));
|
|
12908
|
-
}
|
|
12909
|
-
return 1;
|
|
12910
|
-
}
|
|
12911
|
-
const resolvedConnector = resolveShopifyWorkspaceSourceCommandConnector({
|
|
12912
|
-
payload: connectorsResult.payload,
|
|
12913
|
-
selectedConnectorId,
|
|
12914
|
-
});
|
|
12915
|
-
if (!resolvedConnector.ok) {
|
|
12916
|
-
const payload = {
|
|
12917
|
-
...resolvedConnector.payload,
|
|
12918
|
-
command: command.command,
|
|
12919
|
-
runtimeArtifactId,
|
|
12920
|
-
...(selectedConnectorId ? { selectedConnectorId } : {}),
|
|
12921
|
-
};
|
|
12922
|
-
if (command.json) {
|
|
12923
|
-
io.stdout(JSON.stringify(payload, null, 2));
|
|
12924
|
-
}
|
|
12925
|
-
else {
|
|
12926
|
-
io.stdout(shopifyWorkspaceTextLines(command, payload).join('\n'));
|
|
12927
|
-
}
|
|
12928
|
-
return 1;
|
|
12929
|
-
}
|
|
12930
|
-
const result = await requestShopifyWorkspaceSourceCommand({
|
|
12931
|
-
command: command.command,
|
|
12932
|
-
baseUrl,
|
|
12933
|
-
runtimeArtifactId,
|
|
12934
|
-
fetcher: runtime.fetcher ?? fetch,
|
|
12935
|
-
connectorId: resolvedConnector.connectorId,
|
|
12936
|
-
sourceArtifactId: resolvedConnector.sourceArtifactId,
|
|
12937
|
-
});
|
|
12938
|
-
const payload = {
|
|
12939
|
-
...result.payload,
|
|
12940
12608
|
command: command.command,
|
|
12941
|
-
runtimeArtifactId,
|
|
12942
|
-
selectedConnectorId: resolvedConnector.connectorId,
|
|
12943
|
-
};
|
|
12944
|
-
if (command.json) {
|
|
12945
|
-
io.stdout(JSON.stringify(payload, null, 2));
|
|
12946
|
-
}
|
|
12947
|
-
else {
|
|
12948
|
-
io.stdout(shopifyWorkspaceTextLines(command, payload).join('\n'));
|
|
12949
|
-
}
|
|
12950
|
-
return shopifyWorkspaceCommandSucceeded(command, payload) ? 0 : 1;
|
|
12951
|
-
}
|
|
12952
|
-
function shopifyWorkspaceRequestSucceeded(result) {
|
|
12953
|
-
return result.status >= 200 && result.status < 300 && result.payload.ok !== false;
|
|
12954
|
-
}
|
|
12955
|
-
function shopifyWorkspaceConnectorListTextLines(payload) {
|
|
12956
|
-
const connectors = readShopifyWorkspaceConnectorRecords(payload);
|
|
12957
|
-
const lines = [
|
|
12958
|
-
`runtime_artifact_id: ${String(payload.runtimeArtifactId ?? '')}`,
|
|
12959
|
-
`shop_domain: ${String(payload.shopDomain ?? '')}`,
|
|
12960
|
-
`connectors: ${connectors.length}`,
|
|
12961
|
-
];
|
|
12962
|
-
for (const connector of connectors) {
|
|
12963
|
-
lines.push([
|
|
12964
|
-
String(connector.connectorId ?? ''),
|
|
12965
|
-
String(connector.label ?? connector.appName ?? ''),
|
|
12966
|
-
connector.linked ? 'linked' : 'not_linked',
|
|
12967
|
-
connector.ready ? 'ready' : 'needs_setup',
|
|
12968
|
-
]
|
|
12969
|
-
.filter(Boolean)
|
|
12970
|
-
.join(' | '));
|
|
12971
|
-
}
|
|
12972
|
-
if (typeof payload.reasonCode === 'string') {
|
|
12973
|
-
lines.push(`reason: ${payload.reasonCode}`);
|
|
12974
|
-
}
|
|
12975
|
-
return lines.filter((line) => !line.endsWith(': '));
|
|
12976
|
-
}
|
|
12977
|
-
function shopifyWorkspaceLinkRuntimeTextLines(payload) {
|
|
12978
|
-
const connector = payload.connector && typeof payload.connector === 'object' && !Array.isArray(payload.connector)
|
|
12979
|
-
? payload.connector
|
|
12980
|
-
: {};
|
|
12981
|
-
const source = payload.source && typeof payload.source === 'object' && !Array.isArray(payload.source)
|
|
12982
|
-
? payload.source
|
|
12983
|
-
: {};
|
|
12984
|
-
const workspace = payload.workspace && typeof payload.workspace === 'object' && !Array.isArray(payload.workspace)
|
|
12985
|
-
? payload.workspace
|
|
12986
|
-
: {};
|
|
12987
|
-
return [
|
|
12988
|
-
`link: ${payload.ok === false ? 'failed' : 'created'}`,
|
|
12989
|
-
`runtime_artifact_id: ${String(payload.runtimeArtifactId ?? '')}`,
|
|
12990
|
-
`connector_id: ${String(connector.connectorId ?? '')}`,
|
|
12991
|
-
`source_artifact_id: ${String(source.sourceArtifactId ?? '')}`,
|
|
12992
|
-
workspace.reloadRequired ? 'next: reload the hosted workspace to mount /workspace/shopify-connector' : '',
|
|
12993
|
-
...(payload.reasonCode ? [`reason: ${String(payload.reasonCode)}`] : []),
|
|
12994
|
-
].filter(Boolean);
|
|
12995
|
-
}
|
|
12996
|
-
async function runShopifyWorkspaceConnectorsList(command, io, runtime) {
|
|
12997
|
-
const { baseUrl, runtimeArtifactId } = readShopifyWorkspaceRuntimeIdentity(runtime);
|
|
12998
|
-
const result = await requestShopifyWorkspaceConnectorsList({
|
|
12999
|
-
baseUrl,
|
|
13000
|
-
runtimeArtifactId,
|
|
13001
|
-
fetcher: runtime.fetcher ?? fetch,
|
|
13002
|
-
});
|
|
13003
|
-
const payload = {
|
|
13004
|
-
...result.payload,
|
|
13005
|
-
command: command.command,
|
|
13006
|
-
runtimeArtifactId,
|
|
13007
|
-
};
|
|
13008
|
-
if (command.json) {
|
|
13009
|
-
io.stdout(JSON.stringify(payload, null, 2));
|
|
13010
|
-
}
|
|
13011
|
-
else {
|
|
13012
|
-
io.stdout(shopifyWorkspaceConnectorListTextLines(payload).join('\n'));
|
|
13013
|
-
}
|
|
13014
|
-
return shopifyWorkspaceRequestSucceeded(result) ? 0 : 1;
|
|
13015
|
-
}
|
|
13016
|
-
async function runShopifyWorkspaceLinkRuntime(command, io, runtime) {
|
|
13017
|
-
const { baseUrl, runtimeArtifactId } = readShopifyWorkspaceRuntimeIdentity(runtime);
|
|
13018
|
-
const result = await requestShopifyWorkspaceLinkRuntime({
|
|
13019
|
-
baseUrl,
|
|
13020
|
-
runtimeArtifactId,
|
|
13021
|
-
connectorId: command.connectorId,
|
|
13022
|
-
fetcher: runtime.fetcher ?? fetch,
|
|
13023
|
-
});
|
|
13024
|
-
if (shopifyWorkspaceRequestSucceeded(result)) {
|
|
13025
|
-
await writeHostedWorkspaceConnectorSelection({
|
|
13026
|
-
runtimeArtifactId,
|
|
13027
|
-
connectorId: command.connectorId,
|
|
13028
|
-
}, runtime);
|
|
13029
|
-
}
|
|
13030
|
-
const payload = {
|
|
13031
|
-
...result.payload,
|
|
13032
|
-
command: command.command,
|
|
13033
|
-
runtimeArtifactId,
|
|
13034
12609
|
connectorId: command.connectorId,
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
return shopifyWorkspaceRequestSucceeded(result) ? 0 : 1;
|
|
12610
|
+
sourceArtifactId,
|
|
12611
|
+
dir,
|
|
12612
|
+
exitCode: result.exitCode,
|
|
12613
|
+
stdout: result.stdout,
|
|
12614
|
+
stderr: result.stderr,
|
|
12615
|
+
}, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, 'deploy: completed']);
|
|
12616
|
+
return 0;
|
|
13043
12617
|
}
|
|
13044
12618
|
async function runConnectorsShopifyDelete(command, io, runtime) {
|
|
13045
12619
|
if (command.confirm !== 'delete') {
|
|
@@ -13053,7 +12627,10 @@ async function runConnectorsShopifyDelete(command, io, runtime) {
|
|
|
13053
12627
|
token: credentials.token,
|
|
13054
12628
|
fetcher: runtime.fetcher ?? fetch,
|
|
13055
12629
|
});
|
|
13056
|
-
printConnectorsPayload(command, io, { ...payload, connectorId: command.connectorId }, [
|
|
12630
|
+
printConnectorsPayload(command, io, { ...payload, connectorId: command.connectorId }, [
|
|
12631
|
+
`connector_id: ${command.connectorId}`,
|
|
12632
|
+
'status: deleted',
|
|
12633
|
+
]);
|
|
13057
12634
|
return 0;
|
|
13058
12635
|
}
|
|
13059
12636
|
function runConnectorsBind(command, io) {
|
|
@@ -13880,6 +13457,9 @@ function cliErrorMessage(error) {
|
|
|
13880
13457
|
return String(error);
|
|
13881
13458
|
}
|
|
13882
13459
|
function cliErrorCode(error) {
|
|
13460
|
+
if (error instanceof TenderCliChildProcessError) {
|
|
13461
|
+
return 'child_process_failed';
|
|
13462
|
+
}
|
|
13883
13463
|
if (error instanceof TenderCliDbApiError || error instanceof TenderCliAnalyticsApiError || error instanceof TenderCliConnectorsApiError) {
|
|
13884
13464
|
return error.reasonCode;
|
|
13885
13465
|
}
|
|
@@ -13967,6 +13547,16 @@ function cliErrorPayload(error, args, command) {
|
|
|
13967
13547
|
const scope = scopeForError(error, args, command);
|
|
13968
13548
|
const example = exampleForError(error, args, command);
|
|
13969
13549
|
const nextAction = nextActionForError(error);
|
|
13550
|
+
const childProcess = error instanceof TenderCliChildProcessError
|
|
13551
|
+
? {
|
|
13552
|
+
command: error.command,
|
|
13553
|
+
args: error.args,
|
|
13554
|
+
commandLine: commandLine(error.command, error.args),
|
|
13555
|
+
exitCode: error.exitCode,
|
|
13556
|
+
stdout: error.stdout,
|
|
13557
|
+
stderr: error.stderr,
|
|
13558
|
+
}
|
|
13559
|
+
: null;
|
|
13970
13560
|
return {
|
|
13971
13561
|
ok: false,
|
|
13972
13562
|
command: commandNameForError(args, command),
|
|
@@ -13975,6 +13565,7 @@ function cliErrorPayload(error, args, command) {
|
|
|
13975
13565
|
...(scope ? { scope } : {}),
|
|
13976
13566
|
...(nextAction ? { nextAction } : {}),
|
|
13977
13567
|
...(example ? { example } : {}),
|
|
13568
|
+
...(childProcess ? { childProcess } : {}),
|
|
13978
13569
|
};
|
|
13979
13570
|
}
|
|
13980
13571
|
export async function runTenderCli(args, io = {
|
|
@@ -14078,14 +13669,6 @@ export async function runTenderCli(args, io = {
|
|
|
14078
13669
|
else if (command.topic === 'connectors bind') {
|
|
14079
13670
|
io.stdout(connectorsBindHelp());
|
|
14080
13671
|
}
|
|
14081
|
-
else if (command.topic === 'shopify' ||
|
|
14082
|
-
command.topic === 'shopify workspace' ||
|
|
14083
|
-
command.topic === 'shopify workspace connectors list' ||
|
|
14084
|
-
command.topic === 'shopify workspace link-runtime' ||
|
|
14085
|
-
command.topic === 'shopify workspace validate-source' ||
|
|
14086
|
-
command.topic === 'shopify workspace deploy-source') {
|
|
14087
|
-
io.stdout(shopifyWorkspaceHelp());
|
|
14088
|
-
}
|
|
14089
13672
|
else if (command.topic === 'auth') {
|
|
14090
13673
|
io.stdout(authHelp());
|
|
14091
13674
|
}
|
|
@@ -14346,15 +13929,6 @@ export async function runTenderCli(args, io = {
|
|
|
14346
13929
|
if (command.command === 'connectors shopify source deploy') {
|
|
14347
13930
|
return await runConnectorsShopifySourceDeploy(command, io, runtime);
|
|
14348
13931
|
}
|
|
14349
|
-
if (command.command === 'shopify workspace validate-source' || command.command === 'shopify workspace deploy-source') {
|
|
14350
|
-
return await runShopifyWorkspaceSourceCommand(command, io, runtime);
|
|
14351
|
-
}
|
|
14352
|
-
if (command.command === 'shopify workspace connectors list') {
|
|
14353
|
-
return await runShopifyWorkspaceConnectorsList(command, io, runtime);
|
|
14354
|
-
}
|
|
14355
|
-
if (command.command === 'shopify workspace link-runtime') {
|
|
14356
|
-
return await runShopifyWorkspaceLinkRuntime(command, io, runtime);
|
|
14357
|
-
}
|
|
14358
13932
|
if (command.command === 'connectors shopify delete') {
|
|
14359
13933
|
return await runConnectorsShopifyDelete(command, io, runtime);
|
|
14360
13934
|
}
|
|
@@ -14527,7 +14101,10 @@ export async function runTenderCli(args, io = {
|
|
|
14527
14101
|
}
|
|
14528
14102
|
catch (error) {
|
|
14529
14103
|
if (args.includes('--json') &&
|
|
14530
|
-
((args[0] === 'app' && (args[1] === 'db' || (args[1] === 'agent' && args[2] === 'heartbeat'))) ||
|
|
14104
|
+
((args[0] === 'app' && (args[1] === 'db' || (args[1] === 'agent' && args[2] === 'heartbeat'))) ||
|
|
14105
|
+
args[0] === 'playbooks' ||
|
|
14106
|
+
args[0] === 'connectors' ||
|
|
14107
|
+
args[0] === 'shopify')) {
|
|
14531
14108
|
io.stdout(JSON.stringify(cliErrorPayload(error, args, command), null, 2));
|
|
14532
14109
|
return error instanceof TenderCliUsageError ? 2 : 1;
|
|
14533
14110
|
}
|