1 | #!/usr/bin/env node -r esm
|
2 |
|
3 | import { spawn } from 'child_process';
|
4 | import pkgUp from 'pkg-up';
|
5 | import { readFile, writeFile } from 'jsonfile';
|
6 | import dedent from 'ts-dedent';
|
7 | import chalk from 'chalk';
|
8 |
|
9 | const remainingFlags = process.argv.slice(2);
|
10 |
|
11 | const targets = ['storybook-chromatic', 'storybook-chroma'];
|
12 |
|
13 | const exec = (args, { pipe } = {}) => {
|
14 | return new Promise((res, rej) => {
|
15 | let output = '';
|
16 | const child = spawn('npm', args);
|
17 | const streamHandler = d => {
|
18 | output += d.toString();
|
19 | };
|
20 | child.on('exit', code => {
|
21 | if (code === 0) {
|
22 | res(output);
|
23 | } else {
|
24 | rej(new Error(`${args.join(' ')} exited with code: ${code}`));
|
25 | }
|
26 | });
|
27 |
|
28 | if (pipe) {
|
29 | child.stdin.pipe(process.stdin);
|
30 | child.stdout.pipe(process.stdout);
|
31 | child.stderr.pipe(process.stderr);
|
32 | } else {
|
33 | child.stdout.on('data', streamHandler);
|
34 | child.stderr.on('data', streamHandler);
|
35 | }
|
36 | });
|
37 | };
|
38 |
|
39 | const packageJson = {
|
40 | async read() {
|
41 | return pkgUp(__dirname).then(l => readFile(l));
|
42 | },
|
43 | async write(json) {
|
44 | return pkgUp(__dirname).then(l => writeFile(l, json, { spaces: 2 }));
|
45 | },
|
46 | };
|
47 |
|
48 | const publishAs = async name => {
|
49 | const initial = await packageJson.read();
|
50 |
|
51 | try {
|
52 | const temp = { ...initial, name };
|
53 | await packageJson.write(temp);
|
54 | await exec(['publish', ...remainingFlags], { pipe: true });
|
55 | } catch (e) {
|
56 |
|
57 | } finally {
|
58 | await packageJson.write(initial);
|
59 | }
|
60 | };
|
61 |
|
62 | const check = {
|
63 | async publishable(name, version) {
|
64 | const text = await exec(['info', name, '--json']);
|
65 | const json = JSON.parse(text);
|
66 |
|
67 | const { versions } = json.data ? json.data : json;
|
68 |
|
69 | return !versions.find(v => v === version);
|
70 | },
|
71 |
|
72 | async allowed(name) {
|
73 | const user = (await exec(['whoami'])).trim();
|
74 | const owners = JSON.parse(await exec(['access', 'ls-collaborators', name, '--json']));
|
75 |
|
76 | return !!Object.entries(owners).find(
|
77 | ([useName, permissions]) => useName.match(user) && permissions.includes('write')
|
78 | );
|
79 | },
|
80 | };
|
81 |
|
82 | const getUnpublishable = async list => {
|
83 | const { version } = await packageJson.read();
|
84 |
|
85 | return (await Promise.all(
|
86 | list.map(async name => {
|
87 | const versionOk = await check.publishable(name, version);
|
88 | const ownershipOk = await check.allowed(name);
|
89 |
|
90 | if (versionOk && ownershipOk) {
|
91 | return false;
|
92 | }
|
93 |
|
94 | return { name, reason: versionOk ? 'ownership' : 'version' };
|
95 | })
|
96 | )).reduce((acc, item) => acc.concat(item || []), []);
|
97 | };
|
98 |
|
99 | const run = async list => {
|
100 | const unpublishable = await getUnpublishable(list);
|
101 |
|
102 | if (unpublishable.length === 0) {
|
103 | targets.reduce(async (acc, item) => {
|
104 | await acc;
|
105 |
|
106 | await publishAs(item);
|
107 | }, Promise.resolve());
|
108 | } else {
|
109 | console.log(dedent`
|
110 | ${chalk.red('These packages cannot be published:')}
|
111 |
|
112 | ${unpublishable
|
113 | .map(
|
114 | ({ name, reason }) =>
|
115 | `${chalk.blue(name)} could not be published because of a ${chalk.bold(reason)} problem`
|
116 | )
|
117 | .join('\n')}
|
118 |
|
119 | You may lack the required permissions on npm OR the version you're trying to publish already exists,
|
120 | Upgrade the version or ask permission from current owners
|
121 | `);
|
122 | process.exitCode = 1;
|
123 | }
|
124 | };
|
125 |
|
126 | run(targets);
|