zapier-platform-cli 17.5.0 → 17.7.0

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.
@@ -652,6 +652,13 @@
652
652
  "multiple": false,
653
653
  "type": "option"
654
654
  },
655
+ "paging-token": {
656
+ "description": "Set bundle.meta.paging_token. Used for search pagination or bulk reads. When used in production, this indicates which page of items you should fetch.",
657
+ "name": "paging-token",
658
+ "hasDynamicHelp": false,
659
+ "multiple": false,
660
+ "type": "option"
661
+ },
655
662
  "debug": {
656
663
  "char": "d",
657
664
  "description": "Show extra debugging output.",
@@ -1719,10 +1726,12 @@
1719
1726
  "required": true
1720
1727
  }
1721
1728
  },
1722
- "description": "Create a new canary deployment, diverting a specified percentage of traffic from one version to another for a specified duration.\n\nOnly one canary can be active at the same time. You can run `zapier canary:list` to check. If you would like to create a new canary with different parameters, you can wait for the canary to finish, or delete it using `zapier canary:delete a.b.c x.y.z`.\n\nNote: this is similar to `zapier migrate` but different in that this is temporary and will \"revert\" the changes once the specified duration is expired.\n\n**Only use this command to canary traffic between non-breaking versions!**",
1729
+ "description": "Create a new canary deployment, diverting a specified percentage of traffic from one version to another for a specified duration.\n\nOnly one canary can be active at the same time. You can run `zapier canary:list` to check. If you would like to create a new canary with different parameters, you can wait for the canary to finish, or delete it using `zapier canary:delete a.b.c x.y.z`.\n\nTo canary traffic for a specific user, use the --user flag.\n\nTo canary traffic for an entire account, use the --account-id. Note: this scenario is only permitted for Zapier staff.\n\nTo canary traffic for a specific user within a specific account, use both --user and --account-id flags.\n\nNote: this is similar to `zapier migrate` but different in that this is temporary and will \"revert\" the changes once the specified duration is expired.\n\n**Only use this command to canary traffic between non-breaking versions!**",
1723
1730
  "examples": [
1724
- "zapier canary:create 1.0.0 1.1.0 -p 25 -d 720",
1725
- "zapier canary:create 2.0.0 2.1.0 --percent 50 --duration 300"
1731
+ "zapier canary:create 1.0.0 1.1.0 -p 10 -d 3600",
1732
+ "zapier canary:create 2.0.0 2.1.0 --percent 25 --duration 1800 --user user@example.com",
1733
+ "zapier canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345 -u user@example.com",
1734
+ "zapier canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345"
1726
1735
  ],
1727
1736
  "flags": {
1728
1737
  "percent": {
@@ -1743,6 +1752,29 @@
1743
1752
  "multiple": false,
1744
1753
  "type": "option"
1745
1754
  },
1755
+ "user": {
1756
+ "char": "u",
1757
+ "description": "Canary this user (email) across all accounts, unless `account-id` is specified.",
1758
+ "name": "user",
1759
+ "hasDynamicHelp": false,
1760
+ "multiple": false,
1761
+ "type": "option"
1762
+ },
1763
+ "account-id": {
1764
+ "char": "a",
1765
+ "description": "The account ID to target. If user is specified, only canary the user within this account. If user is not specified, then this argument is only permitted for Zapier staff.",
1766
+ "name": "account-id",
1767
+ "hasDynamicHelp": false,
1768
+ "multiple": false,
1769
+ "type": "option"
1770
+ },
1771
+ "force-include-all": {
1772
+ "char": "f",
1773
+ "description": "Overrides any default filters the canary system imposes. This argument is only permitted for Zapier staff.",
1774
+ "name": "force-include-all",
1775
+ "allowNo": false,
1776
+ "type": "boolean"
1777
+ },
1746
1778
  "debug": {
1747
1779
  "char": "d",
1748
1780
  "description": "Show extra debugging output.",
@@ -1868,88 +1900,6 @@
1868
1900
  "list.js"
1869
1901
  ]
1870
1902
  },
1871
- "delete:integration": {
1872
- "aliases": [
1873
- "delete:app"
1874
- ],
1875
- "args": {},
1876
- "description": "Delete your integration (including all versions).\n\nThis only works if there are no active users or Zaps on any version. If you only want to delete certain versions, use the `zapier delete:version` command instead. It's unlikely that you'll be able to run this on an app that you've pushed publicly, since there are usually still users.",
1877
- "flags": {
1878
- "debug": {
1879
- "char": "d",
1880
- "description": "Show extra debugging output.",
1881
- "name": "debug",
1882
- "allowNo": false,
1883
- "type": "boolean"
1884
- },
1885
- "invokedFromAnotherCommand": {
1886
- "hidden": true,
1887
- "name": "invokedFromAnotherCommand",
1888
- "allowNo": false,
1889
- "type": "boolean"
1890
- }
1891
- },
1892
- "hasDynamicHelp": false,
1893
- "hiddenAliases": [],
1894
- "id": "delete:integration",
1895
- "pluginAlias": "zapier-platform-cli",
1896
- "pluginName": "zapier-platform-cli",
1897
- "pluginType": "core",
1898
- "strict": true,
1899
- "enableJsonFlag": false,
1900
- "skipValidInstallCheck": true,
1901
- "isESM": false,
1902
- "relativePath": [
1903
- "src",
1904
- "oclif",
1905
- "commands",
1906
- "delete",
1907
- "integration.js"
1908
- ]
1909
- },
1910
- "delete:version": {
1911
- "aliases": [],
1912
- "args": {
1913
- "version": {
1914
- "description": "Specify the version to delete. It must have no users or Zaps.",
1915
- "name": "version",
1916
- "required": true
1917
- }
1918
- },
1919
- "description": "Delete a specific version of your integration.\n\nThis only works if there are no users or Zaps on that version. You will probably need to have run `zapier migrate` and `zapier deprecate` before this command will work.",
1920
- "flags": {
1921
- "debug": {
1922
- "char": "d",
1923
- "description": "Show extra debugging output.",
1924
- "name": "debug",
1925
- "allowNo": false,
1926
- "type": "boolean"
1927
- },
1928
- "invokedFromAnotherCommand": {
1929
- "hidden": true,
1930
- "name": "invokedFromAnotherCommand",
1931
- "allowNo": false,
1932
- "type": "boolean"
1933
- }
1934
- },
1935
- "hasDynamicHelp": false,
1936
- "hiddenAliases": [],
1937
- "id": "delete:version",
1938
- "pluginAlias": "zapier-platform-cli",
1939
- "pluginName": "zapier-platform-cli",
1940
- "pluginType": "core",
1941
- "strict": true,
1942
- "enableJsonFlag": false,
1943
- "skipValidInstallCheck": true,
1944
- "isESM": false,
1945
- "relativePath": [
1946
- "src",
1947
- "oclif",
1948
- "commands",
1949
- "delete",
1950
- "version.js"
1951
- ]
1952
- },
1953
1903
  "env:get": {
1954
1904
  "aliases": [],
1955
1905
  "args": {
@@ -2126,6 +2076,88 @@
2126
2076
  "unset.js"
2127
2077
  ]
2128
2078
  },
2079
+ "delete:integration": {
2080
+ "aliases": [
2081
+ "delete:app"
2082
+ ],
2083
+ "args": {},
2084
+ "description": "Delete your integration (including all versions).\n\nThis only works if there are no active users or Zaps on any version. If you only want to delete certain versions, use the `zapier delete:version` command instead. It's unlikely that you'll be able to run this on an app that you've pushed publicly, since there are usually still users.",
2085
+ "flags": {
2086
+ "debug": {
2087
+ "char": "d",
2088
+ "description": "Show extra debugging output.",
2089
+ "name": "debug",
2090
+ "allowNo": false,
2091
+ "type": "boolean"
2092
+ },
2093
+ "invokedFromAnotherCommand": {
2094
+ "hidden": true,
2095
+ "name": "invokedFromAnotherCommand",
2096
+ "allowNo": false,
2097
+ "type": "boolean"
2098
+ }
2099
+ },
2100
+ "hasDynamicHelp": false,
2101
+ "hiddenAliases": [],
2102
+ "id": "delete:integration",
2103
+ "pluginAlias": "zapier-platform-cli",
2104
+ "pluginName": "zapier-platform-cli",
2105
+ "pluginType": "core",
2106
+ "strict": true,
2107
+ "enableJsonFlag": false,
2108
+ "skipValidInstallCheck": true,
2109
+ "isESM": false,
2110
+ "relativePath": [
2111
+ "src",
2112
+ "oclif",
2113
+ "commands",
2114
+ "delete",
2115
+ "integration.js"
2116
+ ]
2117
+ },
2118
+ "delete:version": {
2119
+ "aliases": [],
2120
+ "args": {
2121
+ "version": {
2122
+ "description": "Specify the version to delete. It must have no users or Zaps.",
2123
+ "name": "version",
2124
+ "required": true
2125
+ }
2126
+ },
2127
+ "description": "Delete a specific version of your integration.\n\nThis only works if there are no users or Zaps on that version. You will probably need to have run `zapier migrate` and `zapier deprecate` before this command will work.",
2128
+ "flags": {
2129
+ "debug": {
2130
+ "char": "d",
2131
+ "description": "Show extra debugging output.",
2132
+ "name": "debug",
2133
+ "allowNo": false,
2134
+ "type": "boolean"
2135
+ },
2136
+ "invokedFromAnotherCommand": {
2137
+ "hidden": true,
2138
+ "name": "invokedFromAnotherCommand",
2139
+ "allowNo": false,
2140
+ "type": "boolean"
2141
+ }
2142
+ },
2143
+ "hasDynamicHelp": false,
2144
+ "hiddenAliases": [],
2145
+ "id": "delete:version",
2146
+ "pluginAlias": "zapier-platform-cli",
2147
+ "pluginName": "zapier-platform-cli",
2148
+ "pluginType": "core",
2149
+ "strict": true,
2150
+ "enableJsonFlag": false,
2151
+ "skipValidInstallCheck": true,
2152
+ "isESM": false,
2153
+ "relativePath": [
2154
+ "src",
2155
+ "oclif",
2156
+ "commands",
2157
+ "delete",
2158
+ "version.js"
2159
+ ]
2160
+ },
2129
2161
  "team:add": {
2130
2162
  "aliases": [
2131
2163
  "team:invite"
@@ -2505,5 +2537,5 @@
2505
2537
  ]
2506
2538
  }
2507
2539
  },
2508
- "version": "17.5.0"
2540
+ "version": "17.7.0"
2509
2541
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zapier-platform-cli",
3
- "version": "17.5.0",
3
+ "version": "17.7.0",
4
4
  "description": "The CLI for managing integrations in Zapier Developer Platform.",
5
5
  "repository": "zapier/zapier-platform",
6
6
  "homepage": "https://platform.zapier.com/",
@@ -25,16 +25,16 @@
25
25
  "docs": "ZAPIER_BASE_ENDPOINT='' node scripts/docs.js",
26
26
  "preversion": "git pull && yarn validate",
27
27
  "prepack": "oclif manifest",
28
- "postpack": "rm -f oclif.manifest.json",
28
+ "postpack": "rimraf oclif.manifest.json",
29
29
  "precommit": "yarn docs && git add docs",
30
30
  "version": "yarn docs && git add docs/*",
31
31
  "postversion": "git push && git push --tags",
32
32
  "lint": "eslint src",
33
33
  "lint:fix": "eslint --fix src",
34
- "test": "cross-env NODE_ENV=test mocha -t 50s --recursive src/tests --exit",
35
- "test:debug": "cross-env NODE_ENV=test node inspect ../../node_modules/.bin/mocha -t 50s --recursive src/tests --exit",
36
- "smoke-test": "cross-env NODE_ENV=test mocha -t 2m --recursive src/smoke-tests --exit",
37
- "smoke-test:debug": "cross-env NODE_ENV=test node inspect ../../node_modules/.bin/mocha -t 2m --recursive src/smoke-tests --exit",
34
+ "test": "cross-env NODE_ENV=test mocha -t 200s --recursive src/tests --exit",
35
+ "test:debug": "cross-env NODE_ENV=test node inspect ../../node_modules/.bin/mocha -t 200s --recursive src/tests --exit",
36
+ "smoke-test": "cross-env NODE_ENV=test mocha -t 6m --recursive src/smoke-tests --exit",
37
+ "smoke-test:debug": "cross-env NODE_ENV=test node inspect ../../node_modules/.bin/mocha -t 6m --recursive src/smoke-tests --exit",
38
38
  "validate-templates": "./scripts/validate-app-templates.js",
39
39
  "set-template-versions": "./scripts/set-app-template-versions.js",
40
40
  "validate": "yarn test && yarn smoke-test && yarn lint"
@@ -46,12 +46,12 @@
46
46
  "@oclif/plugin-not-found": "3.2.51",
47
47
  "@oclif/plugin-version": "2.2.28",
48
48
  "adm-zip": "0.5.16",
49
- "decompress": "4.2.1",
50
49
  "archiver": "7.0.1",
51
50
  "chrono-node": "2.8.0",
52
51
  "cli-table3": "0.6.5",
53
52
  "colors": "1.4.0",
54
53
  "debug": "4.4.0",
54
+ "decompress": "4.2.1",
55
55
  "dotenv": "16.5.0",
56
56
  "esbuild": "0.25.4",
57
57
  "fs-extra": "11.2.0",
@@ -73,7 +73,7 @@
73
73
  "semver": "7.7.1",
74
74
  "string-length": "4.0.2",
75
75
  "through2": "4.0.2",
76
- "tmp": "0.2.3",
76
+ "tmp": "0.2.4",
77
77
  "traverse": "0.6.11",
78
78
  "update-notifier": "5.1.0",
79
79
  "yeoman-environment": "3.19.3",
@@ -87,6 +87,7 @@
87
87
  "mock-fs": "^5.5.0",
88
88
  "nock": "^14.0.4",
89
89
  "oclif": "^4.17.46",
90
+ "rimraf": "^5.0.10",
90
91
  "typescript": "^5.8.3",
91
92
  "yamljs": "0.3.0"
92
93
  },
@@ -8,6 +8,9 @@ class CanaryCreateCommand extends ZapierBaseCommand {
8
8
  const { versionFrom, versionTo } = this.args;
9
9
  const percent = this.flags.percent;
10
10
  const duration = this.flags.duration;
11
+ const user = this.flags.user;
12
+ const accountId = this.flags['account-id'];
13
+ const forceIncludeAll = this.flags['force-include-all'];
11
14
 
12
15
  this.validateVersions(versionFrom, versionTo);
13
16
  this.validatePercent(percent);
@@ -25,13 +28,34 @@ If you would like to stop this canary now, run \`zapier canary:delete ${existing
25
28
  return;
26
29
  }
27
30
 
28
- this.startSpinner(`Creating canary deployment
31
+ let createCanaryMessage = `Creating canary deployment
29
32
  - From version: ${versionFrom}
30
33
  - To version: ${versionTo}
31
- - Traffic amount: ${percent}%
32
- - Duration: ${duration} seconds`);
34
+ - Percentage: ${percent}%
35
+ - Duration: ${duration} seconds`;
33
36
 
34
- await createCanary(versionFrom, versionTo, percent, duration);
37
+ const body = {
38
+ percent,
39
+ duration,
40
+ };
41
+
42
+ if (user) {
43
+ body.user = user;
44
+ createCanaryMessage += `\n - User: ${user}`;
45
+ }
46
+
47
+ if (accountId) {
48
+ body.account_id = parseInt(accountId);
49
+ createCanaryMessage += `\n - Account ID: ${accountId}`;
50
+ }
51
+
52
+ if (forceIncludeAll) {
53
+ body.force_include_all = true;
54
+ createCanaryMessage += `\n - Force Include All: true`;
55
+ }
56
+
57
+ this.startSpinner(createCanaryMessage);
58
+ await createCanary(versionFrom, versionTo, body);
35
59
 
36
60
  this.stopSpinner();
37
61
  this.log('Canary deployment created successfully.');
@@ -71,6 +95,21 @@ CanaryCreateCommand.flags = buildFlags({
71
95
  description: 'Duration of the canary in seconds',
72
96
  required: true,
73
97
  }),
98
+ user: Flags.string({
99
+ char: 'u',
100
+ description:
101
+ 'Canary this user (email) across all accounts, unless `account-id` is specified.',
102
+ }),
103
+ 'account-id': Flags.string({
104
+ char: 'a',
105
+ description:
106
+ 'The account ID to target. If user is specified, only canary the user within this account. If user is not specified, then this argument is only permitted for Zapier staff.',
107
+ }),
108
+ 'force-include-all': Flags.boolean({
109
+ char: 'f',
110
+ description:
111
+ 'Overrides any default filters the canary system imposes. This argument is only permitted for Zapier staff.',
112
+ }),
74
113
  },
75
114
  });
76
115
 
@@ -89,13 +128,21 @@ CanaryCreateCommand.description = `Create a new canary deployment, diverting a s
89
128
 
90
129
  Only one canary can be active at the same time. You can run \`zapier canary:list\` to check. If you would like to create a new canary with different parameters, you can wait for the canary to finish, or delete it using \`zapier canary:delete a.b.c x.y.z\`.
91
130
 
131
+ To canary traffic for a specific user, use the --user flag.
132
+
133
+ To canary traffic for an entire account, use the --account-id. Note: this scenario is only permitted for Zapier staff.
134
+
135
+ To canary traffic for a specific user within a specific account, use both --user and --account-id flags.
136
+
92
137
  Note: this is similar to \`zapier migrate\` but different in that this is temporary and will "revert" the changes once the specified duration is expired.
93
138
 
94
139
  **Only use this command to canary traffic between non-breaking versions!**`;
95
140
 
96
141
  CanaryCreateCommand.examples = [
97
- 'zapier canary:create 1.0.0 1.1.0 -p 25 -d 720',
98
- 'zapier canary:create 2.0.0 2.1.0 --percent 50 --duration 300',
142
+ 'zapier canary:create 1.0.0 1.1.0 -p 10 -d 3600',
143
+ 'zapier canary:create 2.0.0 2.1.0 --percent 25 --duration 1800 --user user@example.com',
144
+ 'zapier canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345 -u user@example.com',
145
+ 'zapier canary:create 2.0.0 2.1.0 -p 15 -d 7200 -a 12345',
99
146
  ];
100
147
  CanaryCreateCommand.skipValidInstallCheck = true;
101
148
 
@@ -12,6 +12,8 @@ class CanaryListCommand extends ZapierBaseCommand {
12
12
  to_version: c.to_version,
13
13
  percent: c.percent,
14
14
  seconds_remaining: c.until_timestamp - Math.floor(Date.now() / 1000),
15
+ user: c.user,
16
+ account_id: c.account_id,
15
17
  }));
16
18
 
17
19
  this.log(bold('Active Canaries') + '\n');
@@ -22,6 +24,8 @@ class CanaryListCommand extends ZapierBaseCommand {
22
24
  ['To Version', 'to_version'],
23
25
  ['Traffic Amount', 'percent'],
24
26
  ['Seconds Remaining', 'seconds_remaining'],
27
+ ['User', 'user'],
28
+ ['Account ID', 'account_id'],
25
29
  ],
26
30
  emptyMessage: grey(`No active canary deployments found.`),
27
31
  });
@@ -59,7 +59,14 @@ const loadAuthDataFromEnv = () => {
59
59
  .filter(([k, v]) => k.startsWith(AUTH_FIELD_ENV_PREFIX))
60
60
  .reduce((authData, [k, v]) => {
61
61
  const fieldKey = k.substr(AUTH_FIELD_ENV_PREFIX.length);
62
- authData[fieldKey] = v;
62
+ // Try to parse as JSON if it looks like JSON, otherwise keep as string
63
+ try {
64
+ authData[fieldKey] =
65
+ v.startsWith('{') || v.startsWith('[') ? JSON.parse(v) : v;
66
+ } catch (e) {
67
+ // If JSON parsing fails, keep as string
68
+ authData[fieldKey] = v;
69
+ }
63
70
  return authData;
64
71
  }, {});
65
72
  };
@@ -237,7 +244,10 @@ const appendEnv = async (vars, prefix = '') => {
237
244
  '.env',
238
245
  Object.entries(vars)
239
246
  .filter(([k, v]) => v !== undefined)
240
- .map(([k, v]) => `${prefix}${k}='${v || ''}'\n`),
247
+ .map(
248
+ ([k, v]) =>
249
+ `${prefix}${k}='${typeof v === 'object' && v !== null ? JSON.stringify(v) : v || ''}'\n`,
250
+ ),
241
251
  );
242
252
  };
243
253
 
@@ -1303,6 +1313,7 @@ class InvokeCommand extends BaseCommand {
1303
1313
  isPopulatingDedupe: this.flags.isPopulatingDedupe,
1304
1314
  limit: this.flags.limit,
1305
1315
  page: this.flags.page,
1316
+ paging_token: this.flags['paging-token'],
1306
1317
  isTestingAuth: false, // legacy property
1307
1318
  };
1308
1319
  const output = await this.invokeAction(
@@ -1382,6 +1393,10 @@ InvokeCommand.flags = buildFlags({
1382
1393
  description:
1383
1394
  'EXPERIMENTAL: Instead of using the local .env file, use the production authentication data with the given authentication ID (aka the "app connection" on Zapier). Find them at https://zapier.com/app/assets/connections (https://zpr.io/z8SjFTdnTFZ2 for instructions) or specify \'-\' to interactively select one from your available authentications. When specified, the code will still run locally, but all outgoing requests will be proxied through Zapier with the production auth data.',
1384
1395
  }),
1396
+ 'paging-token': Flags.string({
1397
+ description:
1398
+ 'Set bundle.meta.paging_token. Used for search pagination or bulk reads. When used in production, this indicates which page of items you should fetch.',
1399
+ }),
1385
1400
  },
1386
1401
  });
1387
1402
 
@@ -6,10 +6,18 @@ const yeoman = require('yeoman-environment');
6
6
  const ZapierBaseCommand = require('../ZapierBaseCommand');
7
7
  const { downloadSourceZip } = require('../../utils/api');
8
8
  const { ensureDir, makeTempDir, removeDirSync } = require('../../utils/files');
9
- const { listFiles } = require('../../utils/build');
9
+ const { walkDirWithPresetBlocklist } = require('../../utils/build');
10
10
  const { buildFlags } = require('../buildFlags');
11
11
  const PullGenerator = require('../../generators/pull');
12
12
 
13
+ const listFiles = (dir) => {
14
+ const relPaths = [];
15
+ for (const entry of walkDirWithPresetBlocklist(dir)) {
16
+ relPaths.push(path.join(path.relative(dir, entry.parentPath), entry.name));
17
+ }
18
+ return relPaths;
19
+ };
20
+
13
21
  class PullCommand extends ZapierBaseCommand {
14
22
  async perform() {
15
23
  // Fetch the source zip from API
@@ -28,7 +36,7 @@ class PullCommand extends ZapierBaseCommand {
28
36
 
29
37
  // Prompt user to confirm overwrite
30
38
  const currentDir = process.cwd();
31
- const sourceFiles = await listFiles(srcDst);
39
+ const sourceFiles = listFiles(srcDst);
32
40
 
33
41
  const env = yeoman.createEnv();
34
42
  const namespace = 'zapier:pull';
package/src/utils/api.js CHANGED
@@ -154,17 +154,14 @@ const createCredentials = (username, password, totpCode) => {
154
154
  );
155
155
  };
156
156
 
157
- const createCanary = async (versionFrom, versionTo, percent, duration) => {
157
+ const createCanary = async (versionFrom, versionTo, body) => {
158
158
  const linkedAppId = (await getLinkedAppConfig(undefined, true))?.id;
159
159
 
160
160
  return callAPI(
161
161
  `/apps/${linkedAppId}/versions/${versionFrom}/canary-to/${versionTo}`,
162
162
  {
163
163
  method: 'POST',
164
- body: {
165
- percent,
166
- duration,
167
- },
164
+ body,
168
165
  },
169
166
  );
170
167
  };
@@ -272,13 +272,28 @@ const countLeadingDoubleDots = (relPath) => {
272
272
  // Join all relPaths with workingDir and return the common ancestor directory.
273
273
  const findCommonAncestor = (workingDir, relPaths) => {
274
274
  let maxLeadingDoubleDots = 0;
275
- for (const relPath of relPaths) {
276
- maxLeadingDoubleDots = Math.max(
277
- maxLeadingDoubleDots,
278
- countLeadingDoubleDots(relPath),
279
- );
280
- }
281
275
 
276
+ if (isWindows()) {
277
+ for (const relPath of relPaths) {
278
+ if (relPath.match(/^[a-zA-Z]:/)) {
279
+ // On Windows, relPath can be absolute if it starts with a different
280
+ // drive letter than workingDir.
281
+ return 'C:\\';
282
+ } else {
283
+ maxLeadingDoubleDots = Math.max(
284
+ maxLeadingDoubleDots,
285
+ countLeadingDoubleDots(relPath),
286
+ );
287
+ }
288
+ }
289
+ } else {
290
+ for (const relPath of relPaths) {
291
+ maxLeadingDoubleDots = Math.max(
292
+ maxLeadingDoubleDots,
293
+ countLeadingDoubleDots(relPath),
294
+ );
295
+ }
296
+ }
282
297
  let commonAncestor = workingDir;
283
298
  for (let i = 0; i < maxLeadingDoubleDots; i++) {
284
299
  commonAncestor = path.dirname(commonAncestor);
@@ -286,6 +301,10 @@ const findCommonAncestor = (workingDir, relPaths) => {
286
301
  return commonAncestor;
287
302
  };
288
303
 
304
+ const stripDriveLetterForZip = (pathStr) => {
305
+ return pathStr.replace(/^[cC]:\\/, '').replace(/^([a-zA-Z]):/, '$1');
306
+ };
307
+
289
308
  const writeBuildZipDumbly = async (workingDir, zip) => {
290
309
  for (const entry of walkDirWithPresetBlocklist(workingDir)) {
291
310
  const absPath = path.resolve(entry.parentPath, entry.name);
@@ -350,7 +369,7 @@ const writeBuildZipSmartly = async (workingDir, zip) => {
350
369
  // Write required files to the zip
351
370
  for (const relPath of relPaths) {
352
371
  const absPath = path.resolve(workingDir, relPath);
353
- const nameInZip = path.relative(zipRoot, absPath);
372
+ const nameInZip = stripDriveLetterForZip(path.relative(zipRoot, absPath));
354
373
  if (nameInZip === 'package.json' && zipRoot !== workingDir) {
355
374
  // Ignore workspace root's package.json
356
375
  continue;
@@ -388,10 +407,10 @@ const writeBuildZipSmartly = async (workingDir, zip) => {
388
407
  symlink.parentPath,
389
408
  symlink.name,
390
409
  );
391
- const nameInZip = path.relative(zipRoot, absPath);
410
+ const nameInZip = stripDriveLetterForZip(path.relative(zipRoot, absPath));
392
411
  const targetInZip = path.relative(
393
- symlink.parentPath,
394
- fs.realpathSync(absPath),
412
+ stripDriveLetterForZip(symlink.parentPath),
413
+ stripDriveLetterForZip(fs.realpathSync(absPath)),
395
414
  );
396
415
  zip.symlink(nameInZip, targetInZip, 0o644);
397
416
  }