@linktr.ee/create-link-app 0.1.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.
- package/README.md +171 -0
- package/bin/dev +17 -0
- package/bin/dev.cmd +3 -0
- package/bin/run +5 -0
- package/bin/run.cmd +3 -0
- package/dist/base.js +16 -0
- package/dist/commands/build.js +35 -0
- package/dist/commands/create.js +53 -0
- package/dist/commands/deploy.js +60 -0
- package/dist/commands/dev.js +37 -0
- package/dist/commands/grant-access.js +64 -0
- package/dist/commands/login.js +40 -0
- package/dist/commands/logout.js +30 -0
- package/dist/index.js +5 -0
- package/dist/lib/auth/access-token.js +67 -0
- package/dist/lib/auth/device-auth.js +26 -0
- package/dist/lib/create/create-project.js +17 -0
- package/dist/lib/create/install-dependencies.js +11 -0
- package/dist/lib/create/is-using-yarn.js +6 -0
- package/dist/lib/deploy/create-form-data.js +60 -0
- package/dist/lib/deploy/pack-project.js +55 -0
- package/dist/lib/deploy/upload-assets.js +17 -0
- package/dist/lib/fetch-app-config.js +20 -0
- package/dist/types.js +2 -0
- package/dist/webpack/development.entry.js +59 -0
- package/dist/webpack/production.entry.js +38 -0
- package/dist/webpack/webpack.config.js +113 -0
- package/html-template/development.html +16 -0
- package/html-template/production.html +20 -0
- package/oclif.manifest.json +1 -0
- package/package.json +84 -0
- package/templates/common/.prettierrc +10 -0
- package/templates/common/README.md +89 -0
- package/templates/common/fixtures/props-data.json +4 -0
- package/templates/common/gitignore +3 -0
- package/templates/common/manifest.json +23 -0
- package/templates/common/settings.json +33 -0
- package/templates/common/url-match-rules.json +0 -0
- package/templates/react/package.json +12 -0
- package/templates/react/src/index.jsx +18 -0
- package/templates/react-ts/package.json +14 -0
- package/templates/react-ts/src/images/logo.png +0 -0
- package/templates/react-ts/src/index.tsx +19 -0
- package/templates/react-ts/src/types/global.d.ts +4 -0
- package/templates/react-ts/src/types/index.ts +7 -0
- package/templates/react-ts/tsconfig.json +20 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @linktr.ee/create-link-app
|
|
2
|
+
|
|
3
|
+
CLI tool to create [Link Apps](https://linktr.ee/marketplace) on [Linktr.ee](https://linktr.ee).
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
Create a project with either npm:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm init @linktr.ee/create-link-app my-link-app
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or Yarn:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
yarn create @linktr.ee/create-link-app my-link-app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Commands
|
|
20
|
+
|
|
21
|
+
<!-- commands -->
|
|
22
|
+
* [`create-link-app build`](#create-link-app-build)
|
|
23
|
+
* [`create-link-app create NAME`](#create-link-app-create-name)
|
|
24
|
+
* [`create-link-app deploy`](#create-link-app-deploy)
|
|
25
|
+
* [`create-link-app dev`](#create-link-app-dev)
|
|
26
|
+
* [`create-link-app grant-access LINK_APP_ID USERNAME`](#create-link-app-grant-access-link_app_id-username)
|
|
27
|
+
* [`create-link-app help [COMMAND]`](#create-link-app-help-command)
|
|
28
|
+
* [`create-link-app login`](#create-link-app-login)
|
|
29
|
+
* [`create-link-app logout`](#create-link-app-logout)
|
|
30
|
+
|
|
31
|
+
## `create-link-app build`
|
|
32
|
+
|
|
33
|
+
Build Link App project to static assets used for production
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
USAGE
|
|
37
|
+
$ create-link-app build
|
|
38
|
+
|
|
39
|
+
DESCRIPTION
|
|
40
|
+
Build Link App project to static assets used for production
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## `create-link-app create NAME`
|
|
44
|
+
|
|
45
|
+
Initialize a new Link App project
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
USAGE
|
|
49
|
+
$ create-link-app create [NAME] [-t react|react-ts] [-p <value>]
|
|
50
|
+
|
|
51
|
+
ARGUMENTS
|
|
52
|
+
NAME Name of the Link App
|
|
53
|
+
|
|
54
|
+
FLAGS
|
|
55
|
+
-p, --path=<value> Path to create the project in
|
|
56
|
+
-t, --template=<option> [default: react-ts] Template to use for the project
|
|
57
|
+
<options: react|react-ts>
|
|
58
|
+
|
|
59
|
+
DESCRIPTION
|
|
60
|
+
Initialize a new Link App project
|
|
61
|
+
|
|
62
|
+
EXAMPLES
|
|
63
|
+
$ create-link-app create my-link-app
|
|
64
|
+
|
|
65
|
+
$ create-link-app create my-link-app --template react
|
|
66
|
+
|
|
67
|
+
$ create-link-app create my-link-app --path my/custom/path
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## `create-link-app deploy`
|
|
71
|
+
|
|
72
|
+
Deploy your Link App and test it on Linktr.ee
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
USAGE
|
|
76
|
+
$ create-link-app deploy [-p <value>] [--update]
|
|
77
|
+
|
|
78
|
+
FLAGS
|
|
79
|
+
-p, --path=<value> Specify custom path to project directory
|
|
80
|
+
--update Push update for existing Link App
|
|
81
|
+
|
|
82
|
+
DESCRIPTION
|
|
83
|
+
Deploy your Link App and test it on Linktr.ee
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## `create-link-app dev`
|
|
87
|
+
|
|
88
|
+
Start development server for Link App project
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
USAGE
|
|
92
|
+
$ create-link-app dev [-p <value>] [--https]
|
|
93
|
+
|
|
94
|
+
FLAGS
|
|
95
|
+
-p, --port=<value> [default: 3000] Development server listening port
|
|
96
|
+
--https Use HTTPS for development server
|
|
97
|
+
|
|
98
|
+
DESCRIPTION
|
|
99
|
+
Start development server for Link App project
|
|
100
|
+
|
|
101
|
+
EXAMPLES
|
|
102
|
+
$ create-link-app dev
|
|
103
|
+
|
|
104
|
+
$ create-link-app dev --port 8000
|
|
105
|
+
|
|
106
|
+
$ create-link-app dev --https
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## `create-link-app grant-access LINK_APP_ID USERNAME`
|
|
110
|
+
|
|
111
|
+
Grant access to other developers to push updates for your Link App
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
USAGE
|
|
115
|
+
$ create-link-app grant-access [LINK_APP_ID] [USERNAME]
|
|
116
|
+
|
|
117
|
+
ARGUMENTS
|
|
118
|
+
LINK_APP_ID The Link App's ID you wish to grant access to
|
|
119
|
+
USERNAME The Linktree username of the developer you wish to grant access to
|
|
120
|
+
|
|
121
|
+
DESCRIPTION
|
|
122
|
+
Grant access to other developers to push updates for your Link App
|
|
123
|
+
|
|
124
|
+
EXAMPLES
|
|
125
|
+
$ create-link-app grant-access my-link-app friend
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## `create-link-app help [COMMAND]`
|
|
129
|
+
|
|
130
|
+
Display help for create-link-app.
|
|
131
|
+
|
|
132
|
+
```
|
|
133
|
+
USAGE
|
|
134
|
+
$ create-link-app help [COMMAND] [-n]
|
|
135
|
+
|
|
136
|
+
ARGUMENTS
|
|
137
|
+
COMMAND Command to show help for.
|
|
138
|
+
|
|
139
|
+
FLAGS
|
|
140
|
+
-n, --nested-commands Include all nested commands in the output.
|
|
141
|
+
|
|
142
|
+
DESCRIPTION
|
|
143
|
+
Display help for create-link-app.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
_See code: [@oclif/plugin-help](https://github.com/oclif/plugin-help/blob/v5.1.12/src/commands/help.ts)_
|
|
147
|
+
|
|
148
|
+
## `create-link-app login`
|
|
149
|
+
|
|
150
|
+
Login using your Linktree credentials to deploy Link Apps
|
|
151
|
+
|
|
152
|
+
```
|
|
153
|
+
USAGE
|
|
154
|
+
$ create-link-app login
|
|
155
|
+
|
|
156
|
+
DESCRIPTION
|
|
157
|
+
Login using your Linktree credentials to deploy Link Apps
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## `create-link-app logout`
|
|
161
|
+
|
|
162
|
+
Logout and clear browser session
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
USAGE
|
|
166
|
+
$ create-link-app logout
|
|
167
|
+
|
|
168
|
+
DESCRIPTION
|
|
169
|
+
Logout and clear browser session
|
|
170
|
+
```
|
|
171
|
+
<!-- commandsstop -->
|
package/bin/dev
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const oclif = require('@oclif/core')
|
|
4
|
+
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const project = path.join(__dirname, '..', 'tsconfig.json')
|
|
7
|
+
|
|
8
|
+
// In dev mode -> use ts-node and dev plugins
|
|
9
|
+
process.env.NODE_ENV = 'development'
|
|
10
|
+
|
|
11
|
+
require('ts-node').register({ project })
|
|
12
|
+
|
|
13
|
+
// In dev mode, always show stack traces
|
|
14
|
+
oclif.settings.debug = true
|
|
15
|
+
|
|
16
|
+
// Start the CLI
|
|
17
|
+
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
|
package/bin/dev.cmd
ADDED
package/bin/run
ADDED
package/bin/run.cmd
ADDED
package/dist/base.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const fetch_app_config_1 = __importDefault(require("./lib/fetch-app-config"));
|
|
8
|
+
class default_1 extends core_1.Command {
|
|
9
|
+
async getAppConfig(stage = 'production') {
|
|
10
|
+
return await (0, fetch_app_config_1.default)(stage);
|
|
11
|
+
}
|
|
12
|
+
async init() {
|
|
13
|
+
core_1.CliUx.ux.styledHeader(`${this.config.name}@${this.config.version}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.default = default_1;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const webpack_1 = __importDefault(require("webpack"));
|
|
8
|
+
const base_1 = __importDefault(require("../base"));
|
|
9
|
+
const webpack_config_1 = __importDefault(require("../webpack/webpack.config"));
|
|
10
|
+
class Build extends base_1.default {
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(Build);
|
|
13
|
+
(0, webpack_1.default)((0, webpack_config_1.default)('production', {
|
|
14
|
+
allowAnyOrigin: flags['allow-any-origin'],
|
|
15
|
+
}), (err, stats) => {
|
|
16
|
+
if (err) {
|
|
17
|
+
this.error(err);
|
|
18
|
+
}
|
|
19
|
+
else if (stats?.hasErrors()) {
|
|
20
|
+
this.error(stats.toString());
|
|
21
|
+
}
|
|
22
|
+
else if (stats?.hasWarnings()) {
|
|
23
|
+
this.warn(stats.toString());
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.default = Build;
|
|
29
|
+
Build.description = 'Build Link App project to static assets used for production';
|
|
30
|
+
Build.flags = {
|
|
31
|
+
'allow-any-origin': core_1.Flags.boolean({
|
|
32
|
+
description: 'Allow Link App iframe to be loadable from non-Linktree origins for debugging',
|
|
33
|
+
hidden: true,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const base_1 = __importDefault(require("../base"));
|
|
9
|
+
const create_project_1 = __importDefault(require("../lib/create/create-project"));
|
|
10
|
+
const install_dependencies_1 = __importDefault(require("../lib/create/install-dependencies"));
|
|
11
|
+
const is_using_yarn_1 = __importDefault(require("../lib/create/is-using-yarn"));
|
|
12
|
+
class Create extends base_1.default {
|
|
13
|
+
async run() {
|
|
14
|
+
const { args, flags } = await this.parse(Create);
|
|
15
|
+
const targetDir = path_1.default.resolve(flags.path ?? process.cwd(), args.name);
|
|
16
|
+
this.log(`🔗 Creating project in ${targetDir}...`);
|
|
17
|
+
await (0, create_project_1.default)(flags.template, targetDir);
|
|
18
|
+
const useYarn = (0, is_using_yarn_1.default)();
|
|
19
|
+
core_1.CliUx.ux.action.start('🚚 Installing dependencies');
|
|
20
|
+
(0, install_dependencies_1.default)(targetDir, useYarn);
|
|
21
|
+
core_1.CliUx.ux.action.stop();
|
|
22
|
+
const relativeProjectPath = path_1.default.relative(process.cwd(), targetDir);
|
|
23
|
+
this.log('✅ Project created, to get started:\n');
|
|
24
|
+
this.log(` cd ${relativeProjectPath}`);
|
|
25
|
+
this.log(` ${useYarn ? 'yarn dev' : 'npm run dev'}\n`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.default = Create;
|
|
29
|
+
Create.description = 'Initialize a new Link App project';
|
|
30
|
+
Create.examples = [
|
|
31
|
+
'$ create-link-app create my-link-app',
|
|
32
|
+
'$ create-link-app create my-link-app --template react',
|
|
33
|
+
'$ create-link-app create my-link-app --path my/custom/path',
|
|
34
|
+
];
|
|
35
|
+
Create.args = [
|
|
36
|
+
{
|
|
37
|
+
name: 'name',
|
|
38
|
+
description: 'Name of the Link App',
|
|
39
|
+
required: true,
|
|
40
|
+
},
|
|
41
|
+
];
|
|
42
|
+
Create.flags = {
|
|
43
|
+
template: core_1.Flags.string({
|
|
44
|
+
char: 't',
|
|
45
|
+
description: 'Template to use for the project',
|
|
46
|
+
options: ['react', 'react-ts'],
|
|
47
|
+
default: 'react-ts',
|
|
48
|
+
}),
|
|
49
|
+
path: core_1.Flags.string({
|
|
50
|
+
char: 'p',
|
|
51
|
+
description: 'Path to create the project in',
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const base_1 = __importDefault(require("../base"));
|
|
9
|
+
const access_token_1 = require("../lib/auth/access-token");
|
|
10
|
+
const pack_project_1 = __importDefault(require("../lib/deploy/pack-project"));
|
|
11
|
+
const upload_assets_1 = __importDefault(require("../lib/deploy/upload-assets"));
|
|
12
|
+
const create_form_data_1 = __importDefault(require("../lib/deploy/create-form-data"));
|
|
13
|
+
class Deploy extends base_1.default {
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(Deploy);
|
|
16
|
+
const appConfig = await this.getAppConfig(flags.qa ? 'qa' : 'production');
|
|
17
|
+
const accessToken = (0, access_token_1.getAccessToken)(appConfig.auth.audience);
|
|
18
|
+
await (0, pack_project_1.default)();
|
|
19
|
+
const form = (0, create_form_data_1.default)(flags.path);
|
|
20
|
+
const url = flags.endpoint ?? appConfig.linkTypesUrl;
|
|
21
|
+
try {
|
|
22
|
+
const result = await (0, upload_assets_1.default)(url, form, accessToken, flags.update, flags['force-update']);
|
|
23
|
+
this.log(`Draft Link App successfully ${flags.update ? 'updated' : 'uploaded'}\n`);
|
|
24
|
+
this.log(JSON.stringify(result, null, 2));
|
|
25
|
+
}
|
|
26
|
+
catch (err) {
|
|
27
|
+
this.log('There was an error while uploading Link App assets\n');
|
|
28
|
+
if (axios_1.default.isAxiosError(err)) {
|
|
29
|
+
if (err.response) {
|
|
30
|
+
this.error(JSON.stringify(err.response.data, null, 2));
|
|
31
|
+
}
|
|
32
|
+
this.error(err.message);
|
|
33
|
+
}
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
exports.default = Deploy;
|
|
39
|
+
Deploy.description = 'Deploy your Link App and test it on Linktr.ee';
|
|
40
|
+
Deploy.flags = {
|
|
41
|
+
path: core_1.Flags.string({
|
|
42
|
+
char: 'p',
|
|
43
|
+
description: 'Specify custom path to project directory',
|
|
44
|
+
}),
|
|
45
|
+
update: core_1.Flags.boolean({
|
|
46
|
+
description: 'Push update for existing Link App',
|
|
47
|
+
}),
|
|
48
|
+
endpoint: core_1.Flags.string({
|
|
49
|
+
description: 'Custom API endpoint to push assets to',
|
|
50
|
+
hidden: true,
|
|
51
|
+
}),
|
|
52
|
+
qa: core_1.Flags.boolean({
|
|
53
|
+
description: 'Use QA environment',
|
|
54
|
+
hidden: true,
|
|
55
|
+
}),
|
|
56
|
+
'force-update': core_1.Flags.boolean({
|
|
57
|
+
description: 'Allow Link Type Admins to push updates to Link Apps in PUBLISHED state',
|
|
58
|
+
hidden: true,
|
|
59
|
+
}),
|
|
60
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const webpack_1 = __importDefault(require("webpack"));
|
|
8
|
+
const webpack_dev_server_1 = __importDefault(require("webpack-dev-server"));
|
|
9
|
+
const base_1 = __importDefault(require("../base"));
|
|
10
|
+
const webpack_config_1 = __importDefault(require("../webpack/webpack.config"));
|
|
11
|
+
class Dev extends base_1.default {
|
|
12
|
+
async run() {
|
|
13
|
+
const { flags } = await this.parse(Dev);
|
|
14
|
+
const devServer = new webpack_dev_server_1.default({
|
|
15
|
+
client: {
|
|
16
|
+
overlay: false,
|
|
17
|
+
},
|
|
18
|
+
hot: false,
|
|
19
|
+
https: flags.https,
|
|
20
|
+
port: flags.port,
|
|
21
|
+
}, (0, webpack_1.default)((0, webpack_config_1.default)('development')));
|
|
22
|
+
await devServer.start();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.default = Dev;
|
|
26
|
+
Dev.description = 'Start development server for Link App project';
|
|
27
|
+
Dev.examples = ['$ create-link-app dev', '$ create-link-app dev --port 8000', '$ create-link-app dev --https'];
|
|
28
|
+
Dev.flags = {
|
|
29
|
+
port: core_1.Flags.integer({
|
|
30
|
+
char: 'p',
|
|
31
|
+
description: 'Development server listening port',
|
|
32
|
+
default: 3000,
|
|
33
|
+
}),
|
|
34
|
+
https: core_1.Flags.boolean({
|
|
35
|
+
description: 'Use HTTPS for development server',
|
|
36
|
+
}),
|
|
37
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
const base_1 = __importDefault(require("../base"));
|
|
9
|
+
const access_token_1 = require("../lib/auth/access-token");
|
|
10
|
+
class GrantAccess extends base_1.default {
|
|
11
|
+
async run() {
|
|
12
|
+
const { args, flags } = await this.parse(GrantAccess);
|
|
13
|
+
const appConfig = await this.getAppConfig(flags.qa ? 'qa' : 'production');
|
|
14
|
+
const accessToken = (0, access_token_1.getAccessToken)(appConfig.auth.audience);
|
|
15
|
+
const url = `${flags.endpoint ?? appConfig.linkTypesUrl}/${args.link_app_id}/maintainers`;
|
|
16
|
+
const maintainerDto = {
|
|
17
|
+
username: args.username,
|
|
18
|
+
is_owner: false,
|
|
19
|
+
};
|
|
20
|
+
const headers = {
|
|
21
|
+
'Content-Type': 'application/json',
|
|
22
|
+
Authorization: `Bearer ${accessToken}`,
|
|
23
|
+
};
|
|
24
|
+
try {
|
|
25
|
+
await axios_1.default.post(url, maintainerDto, { headers });
|
|
26
|
+
this.log(`Successfully granted maintainer access to user '${args.username}'\n`);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
this.log('There was an error while attempting to grant access to user\n');
|
|
30
|
+
if (axios_1.default.isAxiosError(err)) {
|
|
31
|
+
if (err.response) {
|
|
32
|
+
this.error(JSON.stringify(err.response.data, null, 2));
|
|
33
|
+
}
|
|
34
|
+
this.error(err.message);
|
|
35
|
+
}
|
|
36
|
+
throw err;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.default = GrantAccess;
|
|
41
|
+
GrantAccess.description = 'Grant access to other developers to push updates for your Link App';
|
|
42
|
+
GrantAccess.examples = ['$ create-link-app grant-access my-link-app friend'];
|
|
43
|
+
GrantAccess.args = [
|
|
44
|
+
{
|
|
45
|
+
name: 'link_app_id',
|
|
46
|
+
description: "The Link App's ID you wish to grant access to",
|
|
47
|
+
required: true,
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
name: 'username',
|
|
51
|
+
description: 'The Linktree username of the developer you wish to grant access to',
|
|
52
|
+
required: true,
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
GrantAccess.flags = {
|
|
56
|
+
endpoint: core_1.Flags.string({
|
|
57
|
+
description: 'Custom API endpoint to push assets to',
|
|
58
|
+
hidden: true,
|
|
59
|
+
}),
|
|
60
|
+
qa: core_1.Flags.boolean({
|
|
61
|
+
description: 'Use QA environment',
|
|
62
|
+
hidden: true,
|
|
63
|
+
}),
|
|
64
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const base_1 = __importDefault(require("../base"));
|
|
8
|
+
const access_token_1 = require("../lib/auth/access-token");
|
|
9
|
+
const device_auth_1 = require("../lib/auth/device-auth");
|
|
10
|
+
class Login extends base_1.default {
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(Login);
|
|
13
|
+
const appConfig = await this.getAppConfig(flags.qa ? 'qa' : 'production');
|
|
14
|
+
const handle = await (0, device_auth_1.initDeviceAuthorization)(appConfig.auth);
|
|
15
|
+
const { expires_in, user_code, verification_uri_complete } = handle;
|
|
16
|
+
const expiryTime = expires_in % 60 === 0 ? `${expires_in / 60} minutes` : `${expires_in} seconds`;
|
|
17
|
+
this.log(`Please login via the opened web browser link. The browser window should display the following code: ${user_code}`);
|
|
18
|
+
this.log(`If browser does not open automatically, please go to the following link: ${verification_uri_complete}`);
|
|
19
|
+
this.log(`This link expires in ${expiryTime}. Press Ctrl-C to abort.\n`);
|
|
20
|
+
core_1.CliUx.ux.open(verification_uri_complete);
|
|
21
|
+
core_1.CliUx.ux.action.start('Waiting for user to authorize device from browser');
|
|
22
|
+
const token = await (0, device_auth_1.pollAccessToken)(handle);
|
|
23
|
+
core_1.CliUx.ux.action.stop();
|
|
24
|
+
if ((0, access_token_1.isTokenValid)(token)) {
|
|
25
|
+
(0, access_token_1.saveAccessToken)(token.access_token);
|
|
26
|
+
this.log('\nDevice has been authorized. Login successful.');
|
|
27
|
+
if (token.expires_at) {
|
|
28
|
+
this.log(`Login will expire at ${new Date(token.expires_at * 1000).toString()}\n`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.default = Login;
|
|
34
|
+
Login.description = 'Login using your Linktree credentials to deploy Link Apps';
|
|
35
|
+
Login.flags = {
|
|
36
|
+
qa: core_1.Flags.boolean({
|
|
37
|
+
description: 'Use QA environment',
|
|
38
|
+
hidden: true,
|
|
39
|
+
}),
|
|
40
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const core_1 = require("@oclif/core");
|
|
7
|
+
const base_1 = __importDefault(require("../base"));
|
|
8
|
+
const access_token_1 = require("../lib/auth/access-token");
|
|
9
|
+
class Logout extends base_1.default {
|
|
10
|
+
async run() {
|
|
11
|
+
const { flags } = await this.parse(Logout);
|
|
12
|
+
const appConfig = await this.getAppConfig(flags.qa ? 'qa' : 'production');
|
|
13
|
+
const { auth, logoutUrl } = appConfig;
|
|
14
|
+
const { domain, client_id, audience } = auth;
|
|
15
|
+
(0, access_token_1.removeAccessToken)(audience);
|
|
16
|
+
// clear Auth0 session cookies so user gets prompted to enter credentials again for next login
|
|
17
|
+
const url = `${domain}/v2/logout?client_id=${client_id}&returnTo=${logoutUrl}`;
|
|
18
|
+
core_1.CliUx.ux.open(url);
|
|
19
|
+
this.log('Logout successful.');
|
|
20
|
+
this.log(`If browser did not open automatically, please go to the following link to logout from the browser: ${url}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.default = Logout;
|
|
24
|
+
Logout.description = 'Logout and clear browser session';
|
|
25
|
+
Logout.flags = {
|
|
26
|
+
qa: core_1.Flags.boolean({
|
|
27
|
+
description: 'Use QA environment',
|
|
28
|
+
hidden: true,
|
|
29
|
+
}),
|
|
30
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getAccessToken = exports.removeAccessToken = exports.saveAccessToken = exports.isTokenValid = void 0;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const netrc_parser_1 = __importDefault(require("netrc-parser"));
|
|
9
|
+
const TokenExpiryThresholdSeconds = 60; // Used to consider tokens as 'expired' within the last x seconds of its actual expiry to ensure slow requests still use a valid token
|
|
10
|
+
function isTokenValid(token) {
|
|
11
|
+
if (!token.access_token || token.token_type !== 'Bearer') {
|
|
12
|
+
throw new Error('Received access token is invalid');
|
|
13
|
+
}
|
|
14
|
+
const payload = jsonwebtoken_1.default.decode(token.access_token, { json: true });
|
|
15
|
+
if (!payload) {
|
|
16
|
+
throw new Error('There was an error parsing the access token, please try logging in again');
|
|
17
|
+
}
|
|
18
|
+
const permissions = payload.permissions ?? [];
|
|
19
|
+
if (permissions.length === 0) {
|
|
20
|
+
throw new Error('Login request was successful, but the authenticated user does not have appropriate permissions to view or modify Link Apps');
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
exports.isTokenValid = isTokenValid;
|
|
25
|
+
function saveAccessToken(tokenString) {
|
|
26
|
+
const token = jsonwebtoken_1.default.decode(tokenString, { json: true });
|
|
27
|
+
const audience = token?.aud;
|
|
28
|
+
if (audience && typeof audience === 'string') {
|
|
29
|
+
netrc_parser_1.default.loadSync();
|
|
30
|
+
netrc_parser_1.default.machines[audience] = {
|
|
31
|
+
login: 'authToken',
|
|
32
|
+
password: tokenString,
|
|
33
|
+
};
|
|
34
|
+
netrc_parser_1.default.saveSync();
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new Error('Error whilst trying to save access token, please try logging in again');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
exports.saveAccessToken = saveAccessToken;
|
|
41
|
+
function removeAccessToken(audience) {
|
|
42
|
+
netrc_parser_1.default.loadSync();
|
|
43
|
+
netrc_parser_1.default.machines[audience] = {
|
|
44
|
+
login: undefined,
|
|
45
|
+
password: undefined,
|
|
46
|
+
};
|
|
47
|
+
netrc_parser_1.default.saveSync();
|
|
48
|
+
}
|
|
49
|
+
exports.removeAccessToken = removeAccessToken;
|
|
50
|
+
function getAccessToken(audience) {
|
|
51
|
+
netrc_parser_1.default.loadSync();
|
|
52
|
+
const tokenString = netrc_parser_1.default.machines[audience]?.login === 'authToken' ? netrc_parser_1.default.machines[audience].password : undefined;
|
|
53
|
+
if (!tokenString) {
|
|
54
|
+
throw new Error('Not logged in. Please login using command: login');
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
const token = jsonwebtoken_1.default.decode(tokenString, { json: true });
|
|
58
|
+
if (!token?.exp) {
|
|
59
|
+
throw new Error('Cached login token is invalid. Please login again using command: login');
|
|
60
|
+
}
|
|
61
|
+
else if (token.exp - TokenExpiryThresholdSeconds < Math.floor(Date.now() / 1000)) {
|
|
62
|
+
throw new Error('Login expired. Please login again using command: login');
|
|
63
|
+
}
|
|
64
|
+
return tokenString;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
exports.getAccessToken = getAccessToken;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.pollAccessToken = exports.initDeviceAuthorization = void 0;
|
|
4
|
+
const openid_client_1 = require("openid-client");
|
|
5
|
+
async function initDeviceAuthorization(config) {
|
|
6
|
+
const { domain, client_id, audience } = config;
|
|
7
|
+
const auth0Issuer = await openid_client_1.Issuer.discover(domain);
|
|
8
|
+
const client = new auth0Issuer.Client({
|
|
9
|
+
client_id,
|
|
10
|
+
token_endpoint_auth_method: 'none',
|
|
11
|
+
});
|
|
12
|
+
return await client.deviceAuthorization({ scope: '', audience });
|
|
13
|
+
}
|
|
14
|
+
exports.initDeviceAuthorization = initDeviceAuthorization;
|
|
15
|
+
async function pollAccessToken(handle) {
|
|
16
|
+
try {
|
|
17
|
+
return await handle.poll();
|
|
18
|
+
}
|
|
19
|
+
catch (err) {
|
|
20
|
+
if (err instanceof openid_client_1.errors.OPError) {
|
|
21
|
+
throw new Error(err.error_description);
|
|
22
|
+
}
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
exports.pollAccessToken = pollAccessToken;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
// templates path relative to this module
|
|
9
|
+
const BaseTemplatesDir = path_1.default.resolve(__dirname, '..', '..', '..', 'templates');
|
|
10
|
+
const createProject = async (template, targetDir) => {
|
|
11
|
+
const templateDir = path_1.default.resolve(BaseTemplatesDir, template);
|
|
12
|
+
const templateCommonDir = path_1.default.resolve(BaseTemplatesDir, 'common');
|
|
13
|
+
await fs_extra_1.default.copy(templateDir, targetDir);
|
|
14
|
+
await fs_extra_1.default.copy(templateCommonDir, targetDir);
|
|
15
|
+
await fs_extra_1.default.move(`${targetDir}/gitignore`, `${targetDir}/.gitignore`);
|
|
16
|
+
};
|
|
17
|
+
exports.default = createProject;
|