@tenderprompt/cli 0.1.36 → 0.1.37

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.
Files changed (2) hide show
  1. package/dist/index.js +107 -2
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process';
3
+ import { createHash } from 'node:crypto';
3
4
  import { readFileSync } from 'node:fs';
4
5
  import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
5
6
  import os from 'node:os';
@@ -164,6 +165,12 @@ class TenderCliChildProcessError extends Error {
164
165
  this.stderr = input.stderr;
165
166
  }
166
167
  }
168
+ class TenderCliDependencyInstallError extends TenderCliChildProcessError {
169
+ constructor(input) {
170
+ super(input);
171
+ this.name = 'TenderCliDependencyInstallError';
172
+ }
173
+ }
167
174
  const DEFAULT_PROFILE_NAME = 'default';
168
175
  const DEFAULT_BASE_URL = 'https://app.tenderprompt.com';
169
176
  const ARTIFACT_SOURCE_EXPORT_MAX_FILES = 1000;
@@ -12288,6 +12295,85 @@ function redactSensitiveText(value, secrets = []) {
12288
12295
  .replace(/\b(authorization\s*[:=]\s*(?:Bearer\s+)?)([^\s'"]+)/gi, '$1[REDACTED]')
12289
12296
  .replace(/\b(x-shopify-access-token\s*[:=]\s*)([^\s'"]+)/gi, '$1[REDACTED]');
12290
12297
  }
12298
+ const SHOPIFY_SOURCE_DEPENDENCY_STATE_VERSION = 1;
12299
+ const SHOPIFY_SOURCE_DEPENDENCY_STATE_FILE = '.tender-shopify-dependencies.json';
12300
+ function dependencyInstallChildEnv(runtime) {
12301
+ const env = shopifyCliChildEnv(runtime);
12302
+ for (const key of Object.keys(env)) {
12303
+ const normalized = key.toUpperCase();
12304
+ if (/(?:TOKEN|SECRET|PASSWORD|PASSWD|AUTHORIZATION|COOKIE|API_KEY|PRIVATE_KEY|CREDENTIAL)/.test(normalized)) {
12305
+ delete env[key];
12306
+ }
12307
+ }
12308
+ return {
12309
+ ...env,
12310
+ NPM_CONFIG_USERCONFIG: '/dev/null',
12311
+ };
12312
+ }
12313
+ async function prepareShopifySourceDependencies(input) {
12314
+ const packageJsonPath = path.join(input.dir, 'package.json');
12315
+ if (!(await fileExists(packageJsonPath))) {
12316
+ return {
12317
+ status: 'not_required',
12318
+ packageManager: null,
12319
+ lockfile: null,
12320
+ installed: false,
12321
+ };
12322
+ }
12323
+ const lockfileName = 'package-lock.json';
12324
+ const lockfilePath = path.join(input.dir, lockfileName);
12325
+ if (!(await fileExists(lockfilePath))) {
12326
+ throw new TenderCliUsageError('Missing package-lock.json. Shopify connector source with package.json must commit a lockfile before validation or deployment.');
12327
+ }
12328
+ const dependencyStateHash = createHash('sha256')
12329
+ .update(await readFile(packageJsonPath))
12330
+ .update('\0')
12331
+ .update(await readFile(lockfilePath))
12332
+ .digest('hex');
12333
+ const statePath = path.join(input.dir, 'node_modules', SHOPIFY_SOURCE_DEPENDENCY_STATE_FILE);
12334
+ try {
12335
+ const state = JSON.parse(await readFile(statePath, 'utf8'));
12336
+ if (typeof state === 'object' &&
12337
+ state !== null &&
12338
+ 'version' in state &&
12339
+ state.version === SHOPIFY_SOURCE_DEPENDENCY_STATE_VERSION &&
12340
+ 'dependencyStateHash' in state &&
12341
+ state.dependencyStateHash === dependencyStateHash) {
12342
+ return {
12343
+ status: 'ready',
12344
+ packageManager: 'npm',
12345
+ lockfile: lockfileName,
12346
+ installed: false,
12347
+ };
12348
+ }
12349
+ }
12350
+ catch {
12351
+ // A missing or stale marker means the locked dependency tree must be rebuilt.
12352
+ }
12353
+ const runner = input.runtime.commandRunner ?? defaultCommandRunner;
12354
+ const args = ['ci', '--ignore-scripts'];
12355
+ const result = await runner('npm', args, {
12356
+ cwd: input.dir,
12357
+ env: dependencyInstallChildEnv(input.runtime),
12358
+ });
12359
+ if (result.exitCode !== 0) {
12360
+ throw new TenderCliDependencyInstallError({
12361
+ command: 'npm',
12362
+ args,
12363
+ exitCode: result.exitCode,
12364
+ stdout: redactSensitiveText(result.stdout),
12365
+ stderr: redactSensitiveText(result.stderr),
12366
+ });
12367
+ }
12368
+ await mkdir(path.dirname(statePath), { recursive: true });
12369
+ await writeFile(statePath, `${JSON.stringify({ version: SHOPIFY_SOURCE_DEPENDENCY_STATE_VERSION, dependencyStateHash })}\n`, 'utf8');
12370
+ return {
12371
+ status: 'ready',
12372
+ packageManager: 'npm',
12373
+ lockfile: lockfileName,
12374
+ installed: true,
12375
+ };
12376
+ }
12291
12377
  async function runShopifyCliCommand(input) {
12292
12378
  const runner = input.runtime.commandRunner ?? defaultCommandRunner;
12293
12379
  const invocation = shopifyCliCommand(input.args);
@@ -12534,6 +12620,7 @@ async function runConnectorsShopifySourceValidate(command, io, runtime) {
12534
12620
  ]);
12535
12621
  return 0;
12536
12622
  }
12623
+ const dependencies = await prepareShopifySourceDependencies({ dir, runtime });
12537
12624
  const validationToken = await resolveShopifyCliTokenEnvironment({
12538
12625
  command,
12539
12626
  sourceArtifactId,
@@ -12557,7 +12644,14 @@ async function runConnectorsShopifySourceValidate(command, io, runtime) {
12557
12644
  exitCode: result.exitCode,
12558
12645
  stdout: result.stdout,
12559
12646
  stderr: result.stderr,
12560
- }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, 'validation: passed']);
12647
+ dependencies,
12648
+ }, [
12649
+ `connector_id: ${command.connectorId}`,
12650
+ `source_artifact_id: ${sourceArtifactId}`,
12651
+ `source_dir: ${dir}`,
12652
+ `dependencies: ${dependencies.status === 'ready' ? (dependencies.installed ? 'installed' : 'cached') : 'not_required'}`,
12653
+ 'validation: passed',
12654
+ ]);
12561
12655
  return 0;
12562
12656
  }
12563
12657
  async function runConnectorsShopifySourceDeploy(command, io, runtime) {
@@ -12595,6 +12689,7 @@ async function runConnectorsShopifySourceDeploy(command, io, runtime) {
12595
12689
  ]);
12596
12690
  return 0;
12597
12691
  }
12692
+ const dependencies = await prepareShopifySourceDependencies({ dir, runtime });
12598
12693
  const deployToken = await resolveShopifyCliTokenEnvironment({
12599
12694
  command,
12600
12695
  sourceArtifactId,
@@ -12612,7 +12707,14 @@ async function runConnectorsShopifySourceDeploy(command, io, runtime) {
12612
12707
  exitCode: result.exitCode,
12613
12708
  stdout: result.stdout,
12614
12709
  stderr: result.stderr,
12615
- }, [`connector_id: ${command.connectorId}`, `source_artifact_id: ${sourceArtifactId}`, `source_dir: ${dir}`, 'deploy: completed']);
12710
+ dependencies,
12711
+ }, [
12712
+ `connector_id: ${command.connectorId}`,
12713
+ `source_artifact_id: ${sourceArtifactId}`,
12714
+ `source_dir: ${dir}`,
12715
+ `dependencies: ${dependencies.status === 'ready' ? (dependencies.installed ? 'installed' : 'cached') : 'not_required'}`,
12716
+ 'deploy: completed',
12717
+ ]);
12616
12718
  return 0;
12617
12719
  }
12618
12720
  async function runConnectorsShopifyDelete(command, io, runtime) {
@@ -13457,6 +13559,9 @@ function cliErrorMessage(error) {
13457
13559
  return String(error);
13458
13560
  }
13459
13561
  function cliErrorCode(error) {
13562
+ if (error instanceof TenderCliDependencyInstallError) {
13563
+ return 'dependency_install_failed';
13564
+ }
13460
13565
  if (error instanceof TenderCliChildProcessError) {
13461
13566
  return 'child_process_failed';
13462
13567
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenderprompt/cli",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tender": "bin/tender.js"