@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
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const child_process_1 = require("child_process");
|
|
4
|
+
const installDependencies = (targetDir, useYarn) => {
|
|
5
|
+
const command = useYarn ? 'yarn' : 'npm';
|
|
6
|
+
const args = useYarn ? [] : ['install'];
|
|
7
|
+
(0, child_process_1.spawnSync)(command, args, {
|
|
8
|
+
cwd: targetDir,
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
exports.default = installDependencies;
|
|
@@ -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 form_data_1 = __importDefault(require("form-data"));
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const RequiredAssets = [
|
|
10
|
+
{
|
|
11
|
+
key: 'manifest',
|
|
12
|
+
fileName: 'manifest.json',
|
|
13
|
+
contentType: 'application/json',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: 'settings',
|
|
17
|
+
fileName: 'settings.json',
|
|
18
|
+
contentType: 'application/json',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
key: 'package',
|
|
22
|
+
fileName: 'package.tgz',
|
|
23
|
+
contentType: 'application/gzip',
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
const OptionalAssets = [
|
|
27
|
+
{
|
|
28
|
+
key: 'icon',
|
|
29
|
+
fileName: 'icon.svg',
|
|
30
|
+
contentType: 'image/svg+xml',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: 'url_match_rules',
|
|
34
|
+
fileName: 'url_match_rules.json',
|
|
35
|
+
contentType: 'application/json',
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
/**
|
|
39
|
+
* Create a readable multipart/form-data stream which includes the Link App asset files.
|
|
40
|
+
*/
|
|
41
|
+
function createFormData(assetPath = process.cwd()) {
|
|
42
|
+
const form = new form_data_1.default();
|
|
43
|
+
RequiredAssets.forEach((asset) => {
|
|
44
|
+
const filePath = path_1.default.resolve(assetPath, asset.fileName);
|
|
45
|
+
if (!fs_1.default.existsSync(filePath)) {
|
|
46
|
+
throw new Error(`Missing asset file: ${asset.fileName}`);
|
|
47
|
+
}
|
|
48
|
+
const stream = fs_1.default.createReadStream(filePath);
|
|
49
|
+
form.append(asset.key, stream, { contentType: asset.contentType });
|
|
50
|
+
});
|
|
51
|
+
OptionalAssets.forEach((asset) => {
|
|
52
|
+
const filePath = path_1.default.resolve(assetPath, asset.fileName);
|
|
53
|
+
if (fs_1.default.existsSync(filePath)) {
|
|
54
|
+
const stream = fs_1.default.createReadStream(filePath);
|
|
55
|
+
form.append(asset.key, stream, { contentType: asset.contentType });
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
return form;
|
|
59
|
+
}
|
|
60
|
+
exports.default = createFormData;
|
|
@@ -0,0 +1,55 @@
|
|
|
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 archiver_1 = __importDefault(require("archiver"));
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
/**
|
|
10
|
+
* Pack the Link App source files into `package.tgz`. This includes the following:
|
|
11
|
+
* - `src/`
|
|
12
|
+
* - `package.json`
|
|
13
|
+
* - `package-lock.json` - *optional*
|
|
14
|
+
* - `yarn.lock` - *optional*
|
|
15
|
+
* - `tsconfig.json` - *optional*
|
|
16
|
+
*/
|
|
17
|
+
async function packProject() {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const outputPath = resolveProjPath('package.tgz');
|
|
20
|
+
const outputWriteStream = fs_1.default.createWriteStream(outputPath);
|
|
21
|
+
const handleSuccess = () => resolve();
|
|
22
|
+
const handleFailure = (err) => {
|
|
23
|
+
// remove success handler because 'close' is still emitted on error
|
|
24
|
+
outputWriteStream.removeListener('close', handleSuccess);
|
|
25
|
+
reject(err);
|
|
26
|
+
};
|
|
27
|
+
outputWriteStream.on('close', handleSuccess);
|
|
28
|
+
const archive = (0, archiver_1.default)('tar', {
|
|
29
|
+
gzip: true,
|
|
30
|
+
gzipOptions: { level: 9 },
|
|
31
|
+
});
|
|
32
|
+
archive.on('warning', (err) => {
|
|
33
|
+
if (err.code === 'ENOENT') {
|
|
34
|
+
// lock files and tsconfig may or may not exist
|
|
35
|
+
const fileName = path_1.default.basename(err.path);
|
|
36
|
+
if (fileName === 'package-lock.json' || fileName === 'yarn.lock' || fileName === 'tsconfig.json') {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
handleFailure(err);
|
|
41
|
+
});
|
|
42
|
+
archive.on('error', (err) => {
|
|
43
|
+
handleFailure(err);
|
|
44
|
+
});
|
|
45
|
+
archive.pipe(outputWriteStream);
|
|
46
|
+
archive.file(resolveProjPath('package.json'), { name: 'package.json' });
|
|
47
|
+
archive.file(resolveProjPath('package-lock.json'), { name: 'package-lock.json' });
|
|
48
|
+
archive.file(resolveProjPath('yarn.lock'), { name: 'yarn.lock' });
|
|
49
|
+
archive.file(resolveProjPath('tsconfig.json'), { name: 'tsconfig.json' });
|
|
50
|
+
archive.directory(resolveProjPath('src/'), 'src');
|
|
51
|
+
archive.finalize();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
const resolveProjPath = (relativePath) => path_1.default.resolve(process.cwd(), relativePath);
|
|
55
|
+
exports.default = packProject;
|
|
@@ -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 axios_1 = __importDefault(require("axios"));
|
|
7
|
+
async function uploadAssets(url, formData, accessToken, update, forceUpdate) {
|
|
8
|
+
const uploadMethod = update ? axios_1.default.put : axios_1.default.post;
|
|
9
|
+
const headers = formData.getHeaders();
|
|
10
|
+
headers['Authorization'] = `Bearer ${accessToken}`;
|
|
11
|
+
if (update && forceUpdate) {
|
|
12
|
+
headers['force-update'] = 'true';
|
|
13
|
+
}
|
|
14
|
+
const res = await uploadMethod(url, formData, { headers });
|
|
15
|
+
return res.data;
|
|
16
|
+
}
|
|
17
|
+
exports.default = uploadAssets;
|
|
@@ -0,0 +1,20 @@
|
|
|
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 axios_1 = __importDefault(require("axios"));
|
|
7
|
+
const fetchAppConfig = async (stage = 'production') => {
|
|
8
|
+
const configUrl = `https://link-types-assets.${stage}.linktr.ee/application-config/create-link-cli.json`;
|
|
9
|
+
try {
|
|
10
|
+
const response = await axios_1.default.get(configUrl);
|
|
11
|
+
return response.data;
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
if (axios_1.default.isAxiosError(err)) {
|
|
15
|
+
throw new Error(err.message);
|
|
16
|
+
}
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
exports.default = fetchAppConfig;
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
const react_1 = __importStar(require("react"));
|
|
30
|
+
const react_dom_1 = __importDefault(require("react-dom"));
|
|
31
|
+
const extension_1 = __importDefault(require("@linktr.ee/extension"));
|
|
32
|
+
const extension_dev_data_1 = __importDefault(require("@linktr.ee/extension-dev-data"));
|
|
33
|
+
const root = document.getElementById('root');
|
|
34
|
+
const App = () => {
|
|
35
|
+
// TODO: refactor common postMessage to parent in both entry points
|
|
36
|
+
(0, react_1.useEffect)(() => {
|
|
37
|
+
const message = {
|
|
38
|
+
type: 'extension-ready',
|
|
39
|
+
data: {
|
|
40
|
+
ready: true,
|
|
41
|
+
height: root.clientHeight,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
window.parent.postMessage(message, '*');
|
|
45
|
+
}, []);
|
|
46
|
+
if (window.location.search === '?embed') {
|
|
47
|
+
return react_1.default.createElement(extension_1.default, { ...extension_dev_data_1.default });
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
return (react_1.default.createElement("iframe", { src: `${window.location}?embed`, sandbox: ['allow-scripts', 'allow-same-origin', 'allow-popups', 'allow-popups-to-escape-sandbox'].join(' '), scrolling: "no", frameBorder: 0, style: {
|
|
51
|
+
display: 'block',
|
|
52
|
+
margin: '0 auto',
|
|
53
|
+
width: '1px',
|
|
54
|
+
minWidth: '680px',
|
|
55
|
+
height: '100vh',
|
|
56
|
+
} }));
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
react_dom_1.default.render(react_1.default.createElement(App, null), root);
|
|
@@ -0,0 +1,38 @@
|
|
|
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 react_1 = __importDefault(require("react"));
|
|
7
|
+
const react_dom_1 = __importDefault(require("react-dom"));
|
|
8
|
+
const extension_1 = __importDefault(require("@linktr.ee/extension"));
|
|
9
|
+
const root = document.getElementById('root');
|
|
10
|
+
// First set up event handler for extension data
|
|
11
|
+
window.addEventListener('message', function handler(event) {
|
|
12
|
+
if (!__ALLOW_ANY_ORIGIN__) {
|
|
13
|
+
if (!/^https:\/\/(qa\.)?linktr.ee$/.test(event.origin))
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (event.data?.type !== 'extension-data')
|
|
17
|
+
return;
|
|
18
|
+
renderApp(event.data?.data ?? {});
|
|
19
|
+
window.removeEventListener('message', handler);
|
|
20
|
+
});
|
|
21
|
+
// Then send extension-loaded event
|
|
22
|
+
window.parent.postMessage({ type: 'extension-loaded' }, '*');
|
|
23
|
+
// Send interaction events on components to the parent window for processing
|
|
24
|
+
document.addEventListener('interaction-event', (event) => window.parent.postMessage({ type: 'interaction-event', data: event.detail }, '*'));
|
|
25
|
+
const renderApp = (data) => {
|
|
26
|
+
const App = () => react_1.default.createElement(extension_1.default, { ...data });
|
|
27
|
+
react_dom_1.default.render(react_1.default.createElement(App, null), root, postExtensionReadyMessage);
|
|
28
|
+
};
|
|
29
|
+
const postExtensionReadyMessage = () => {
|
|
30
|
+
const message = {
|
|
31
|
+
type: 'extension-ready',
|
|
32
|
+
data: {
|
|
33
|
+
ready: true,
|
|
34
|
+
height: root.clientHeight,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
window.parent.postMessage(message, '*');
|
|
38
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const fork_ts_checker_webpack_plugin_1 = __importDefault(require("fork-ts-checker-webpack-plugin"));
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const html_webpack_plugin_1 = __importDefault(require("html-webpack-plugin"));
|
|
10
|
+
const inject_body_webpack_plugin_1 = __importDefault(require("inject-body-webpack-plugin"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const webpack_1 = require("webpack");
|
|
13
|
+
function default_1(env, options) {
|
|
14
|
+
const appDir = process.cwd();
|
|
15
|
+
const entryFile = path_1.default.resolve(__dirname, `${env}.entry`);
|
|
16
|
+
const htmlTemplateFile = path_1.default.resolve(__dirname, '..', '..', 'html-template', `${env}.html`);
|
|
17
|
+
const hasTsconfig = fileExists(path_1.default.resolve(appDir, 'tsconfig.json'));
|
|
18
|
+
const config = {
|
|
19
|
+
target: 'web',
|
|
20
|
+
mode: env,
|
|
21
|
+
entry: entryFile,
|
|
22
|
+
output: {
|
|
23
|
+
publicPath: '',
|
|
24
|
+
path: path_1.default.resolve(appDir, 'dist'),
|
|
25
|
+
assetModuleFilename: 'images/[hash][ext][query]',
|
|
26
|
+
},
|
|
27
|
+
module: {
|
|
28
|
+
rules: [
|
|
29
|
+
{
|
|
30
|
+
test: /\.(js|jsx|ts|tsx)$/,
|
|
31
|
+
exclude: /node_modules/,
|
|
32
|
+
loader: require.resolve('babel-loader'),
|
|
33
|
+
options: {
|
|
34
|
+
presets: [
|
|
35
|
+
require.resolve('@babel/preset-env'),
|
|
36
|
+
require.resolve('@babel/preset-typescript'),
|
|
37
|
+
[require.resolve('@babel/preset-react'), { runtime: 'automatic' }],
|
|
38
|
+
],
|
|
39
|
+
plugins: [require.resolve('@babel/plugin-transform-runtime')],
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
test: /\.(png|jpe?g|gif)$/i,
|
|
44
|
+
type: 'asset/resource',
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
resolve: {
|
|
49
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx'],
|
|
50
|
+
// Resolve `react` and `react-dom` in entry file from the app modules
|
|
51
|
+
// TODO: The behaviour here with module resolution is confusing, can we make it more explicit?
|
|
52
|
+
modules: [path_1.default.resolve(appDir, 'node_modules'), 'node_modules'],
|
|
53
|
+
alias: {
|
|
54
|
+
'@linktr.ee/extension': path_1.default.resolve(appDir, 'src'),
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
plugins: [
|
|
58
|
+
new html_webpack_plugin_1.default({
|
|
59
|
+
template: htmlTemplateFile,
|
|
60
|
+
}),
|
|
61
|
+
],
|
|
62
|
+
};
|
|
63
|
+
// Type checking on a separate process - @babel/preset-typescript only handles transpilation
|
|
64
|
+
if (hasTsconfig) {
|
|
65
|
+
config.plugins.push(new fork_ts_checker_webpack_plugin_1.default());
|
|
66
|
+
}
|
|
67
|
+
if (env === 'development') {
|
|
68
|
+
config.devtool = 'cheap-module-source-map';
|
|
69
|
+
config.resolve.alias = {
|
|
70
|
+
...config.resolve.alias,
|
|
71
|
+
'@linktr.ee/extension-dev-data': path_1.default.resolve(appDir, 'fixtures', 'props-data.json'),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (env === 'production') {
|
|
75
|
+
config.externals = {
|
|
76
|
+
react: 'React',
|
|
77
|
+
'react-dom': 'ReactDOM',
|
|
78
|
+
'styled-components': 'styled',
|
|
79
|
+
};
|
|
80
|
+
config.performance = {
|
|
81
|
+
maxEntrypointSize: 1048576,
|
|
82
|
+
maxAssetSize: 350000,
|
|
83
|
+
hints: 'warning',
|
|
84
|
+
};
|
|
85
|
+
config.plugins.push(new webpack_1.DefinePlugin({
|
|
86
|
+
__ALLOW_ANY_ORIGIN__: options?.allowAnyOrigin,
|
|
87
|
+
}),
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
89
|
+
// @ts-ignore
|
|
90
|
+
new inject_body_webpack_plugin_1.default({
|
|
91
|
+
position: 'start',
|
|
92
|
+
content: `<!-- ${getBuildInfo()} -->`,
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
return config;
|
|
96
|
+
}
|
|
97
|
+
exports.default = default_1;
|
|
98
|
+
const getBuildInfo = () => {
|
|
99
|
+
const buildInfo = [`built at: ${new Date().toISOString()}`];
|
|
100
|
+
if (typeof process.env?.BUILDKITE_BUILD_ID === 'string' && process.env.BUILDKITE_BUILD_ID.length) {
|
|
101
|
+
buildInfo.push(`buildId: ${process.env.BUILDKITE_BUILD_ID}`);
|
|
102
|
+
}
|
|
103
|
+
return buildInfo.join(' ');
|
|
104
|
+
};
|
|
105
|
+
const fileExists = (path) => {
|
|
106
|
+
try {
|
|
107
|
+
fs_1.default.accessSync(path);
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<base target="_blank" />
|
|
6
|
+
<meta charset="utf-8" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
+
<script crossorigin src="https://unpkg.com/iframe-resizer@4.3.2/js/iframeResizer.contentWindow.js"></script>
|
|
9
|
+
<title>Extension Link</title>
|
|
10
|
+
</head>
|
|
11
|
+
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
</body>
|
|
15
|
+
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<base target="_blank" />
|
|
6
|
+
<meta charset="utf-8" />
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
8
|
+
<title>Extension Link</title>
|
|
9
|
+
<script crossorigin src="https://unpkg.com/react-is@17.0.2/umd/react-is.production.min.js"></script>
|
|
10
|
+
<script crossorigin src="https://unpkg.com/react@17.0.2/umd/react.production.min.js"></script>
|
|
11
|
+
<script crossorigin src="https://unpkg.com/react-dom@17.0.2/umd/react-dom.production.min.js"></script>
|
|
12
|
+
<script crossorigin src="https://unpkg.com/styled-components@5.2.1/dist/styled-components.js"></script>
|
|
13
|
+
<script crossorigin src="https://unpkg.com/iframe-resizer@4.3.2/js/iframeResizer.contentWindow.min.js"></script>
|
|
14
|
+
</head>
|
|
15
|
+
|
|
16
|
+
<body>
|
|
17
|
+
<div id="root"></div>
|
|
18
|
+
</body>
|
|
19
|
+
|
|
20
|
+
</html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"0.1.0","commands":{"build":{"id":"build","description":"Build Link App project to static assets used for production","strict":true,"pluginName":"@linktr.ee/create-link-app","pluginAlias":"@linktr.ee/create-link-app","pluginType":"core","aliases":[],"flags":{"allow-any-origin":{"name":"allow-any-origin","type":"boolean","description":"Allow Link App iframe to be loadable from non-Linktree origins for debugging","hidden":true,"allowNo":false}},"args":[],"_globalFlags":{}},"create":{"id":"create","description":"Initialize a new Link App project","strict":true,"pluginName":"@linktr.ee/create-link-app","pluginAlias":"@linktr.ee/create-link-app","pluginType":"core","aliases":[],"examples":["$ create-link-app create my-link-app","$ create-link-app create my-link-app --template react","$ create-link-app create my-link-app --path my/custom/path"],"flags":{"template":{"name":"template","type":"option","char":"t","description":"Template to use for the project","multiple":false,"options":["react","react-ts"],"default":"react-ts"},"path":{"name":"path","type":"option","char":"p","description":"Path to create the project in","multiple":false}},"args":[{"name":"name","description":"Name of the Link App","required":true}],"_globalFlags":{}},"deploy":{"id":"deploy","description":"Deploy your Link App and test it on Linktr.ee","strict":true,"pluginName":"@linktr.ee/create-link-app","pluginAlias":"@linktr.ee/create-link-app","pluginType":"core","aliases":[],"flags":{"path":{"name":"path","type":"option","char":"p","description":"Specify custom path to project directory","multiple":false},"update":{"name":"update","type":"boolean","description":"Push update for existing Link App","allowNo":false},"endpoint":{"name":"endpoint","type":"option","description":"Custom API endpoint to push assets to","hidden":true,"multiple":false},"qa":{"name":"qa","type":"boolean","description":"Use QA environment","hidden":true,"allowNo":false},"force-update":{"name":"force-update","type":"boolean","description":"Allow Link Type Admins to push updates to Link Apps in PUBLISHED state","hidden":true,"allowNo":false}},"args":[],"_globalFlags":{}},"dev":{"id":"dev","description":"Start development server for Link App project","strict":true,"pluginName":"@linktr.ee/create-link-app","pluginAlias":"@linktr.ee/create-link-app","pluginType":"core","aliases":[],"examples":["$ create-link-app dev","$ create-link-app dev --port 8000","$ create-link-app dev --https"],"flags":{"port":{"name":"port","type":"option","char":"p","description":"Development server listening port","multiple":false,"default":3000},"https":{"name":"https","type":"boolean","description":"Use HTTPS for development server","allowNo":false}},"args":[],"_globalFlags":{}},"grant-access":{"id":"grant-access","description":"Grant access to other developers to push updates for your Link App","strict":true,"pluginName":"@linktr.ee/create-link-app","pluginAlias":"@linktr.ee/create-link-app","pluginType":"core","aliases":[],"examples":["$ create-link-app grant-access my-link-app friend"],"flags":{"endpoint":{"name":"endpoint","type":"option","description":"Custom API endpoint to push assets to","hidden":true,"multiple":false},"qa":{"name":"qa","type":"boolean","description":"Use QA environment","hidden":true,"allowNo":false}},"args":[{"name":"link_app_id","description":"The Link App's ID you wish to grant access to","required":true},{"name":"username","description":"The Linktree username of the developer you wish to grant access to","required":true}],"_globalFlags":{}},"login":{"id":"login","description":"Login using your Linktree credentials to deploy Link Apps","strict":true,"pluginName":"@linktr.ee/create-link-app","pluginAlias":"@linktr.ee/create-link-app","pluginType":"core","aliases":[],"flags":{"qa":{"name":"qa","type":"boolean","description":"Use QA environment","hidden":true,"allowNo":false}},"args":[],"_globalFlags":{}},"logout":{"id":"logout","description":"Logout and clear browser session","strict":true,"pluginName":"@linktr.ee/create-link-app","pluginAlias":"@linktr.ee/create-link-app","pluginType":"core","aliases":[],"flags":{"qa":{"name":"qa","type":"boolean","description":"Use QA environment","hidden":true,"allowNo":false}},"args":[],"_globalFlags":{}}}}
|
package/package.json
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@linktr.ee/create-link-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Create a Link App on Linktr.ee.",
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"author": "Linktree",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=12.0.0"
|
|
9
|
+
},
|
|
10
|
+
"bin": {
|
|
11
|
+
"create-link-app": "./bin/run"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"/bin",
|
|
15
|
+
"/dist",
|
|
16
|
+
"/html-template",
|
|
17
|
+
"/npm-shrinkwrap.json",
|
|
18
|
+
"/oclif.manifest.json",
|
|
19
|
+
"/templates"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "shx rm -rf dist && tsc -b",
|
|
23
|
+
"postpack": "shx rm -f oclif.manifest.json",
|
|
24
|
+
"prepack": "yarn build && oclif manifest && oclif readme",
|
|
25
|
+
"test": "jest --watch",
|
|
26
|
+
"test:ci": "jest",
|
|
27
|
+
"test:cov": "jest --coverage",
|
|
28
|
+
"version": "oclif readme && git add README.md"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@babel/core": "^7.18.2",
|
|
32
|
+
"@babel/plugin-transform-runtime": "^7.18.2",
|
|
33
|
+
"@babel/preset-env": "^7.18.2",
|
|
34
|
+
"@babel/preset-react": "^7.17.12",
|
|
35
|
+
"@babel/preset-typescript": "^7.17.12",
|
|
36
|
+
"@babel/runtime": "^7.18.3",
|
|
37
|
+
"@oclif/core": "^1.9.0",
|
|
38
|
+
"@oclif/plugin-help": "^5.1.12",
|
|
39
|
+
"archiver": "^5.3.1",
|
|
40
|
+
"axios": "^0.27.2",
|
|
41
|
+
"babel-loader": "^8.2.5",
|
|
42
|
+
"fork-ts-checker-webpack-plugin": "^7.2.11",
|
|
43
|
+
"form-data": "^4.0.0",
|
|
44
|
+
"fs-extra": "^10.1.0",
|
|
45
|
+
"html-webpack-plugin": "^5.5.0",
|
|
46
|
+
"inject-body-webpack-plugin": "^1.3.0",
|
|
47
|
+
"jsonwebtoken": "^8.5.1",
|
|
48
|
+
"netrc-parser": "^3.1.6",
|
|
49
|
+
"openid-client": "^5.1.6",
|
|
50
|
+
"react": "^17.0.2",
|
|
51
|
+
"react-dom": "^17.0.2",
|
|
52
|
+
"styled-components": "^5.3.5",
|
|
53
|
+
"typescript": "^4.7.3",
|
|
54
|
+
"webpack": "^5.73.0",
|
|
55
|
+
"webpack-dev-server": "^4.9.2"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@oclif/test": "^2.1.0",
|
|
59
|
+
"@types/archiver": "^5.3.1",
|
|
60
|
+
"@types/axios": "^0.14.0",
|
|
61
|
+
"@types/form-data": "^2.5.0",
|
|
62
|
+
"@types/fs-extra": "^9.0.13",
|
|
63
|
+
"@types/jest": "^28.1.1",
|
|
64
|
+
"@types/jsonwebtoken": "^8.5.8",
|
|
65
|
+
"@types/react": "^17.0.45",
|
|
66
|
+
"@types/react-dom": "^17.0.17",
|
|
67
|
+
"axios-mock-adapter": "^1.21.1",
|
|
68
|
+
"jest": "^28.1.1",
|
|
69
|
+
"oclif": "^3.0.1",
|
|
70
|
+
"shx": "^0.3.4",
|
|
71
|
+
"ts-jest": "^28.0.4",
|
|
72
|
+
"ts-node": "^10.8.1",
|
|
73
|
+
"tslib": "^2.4.0"
|
|
74
|
+
},
|
|
75
|
+
"oclif": {
|
|
76
|
+
"bin": "create-link-app",
|
|
77
|
+
"dirname": "create-link-app",
|
|
78
|
+
"default": "create",
|
|
79
|
+
"commands": "./dist/commands",
|
|
80
|
+
"plugins": [
|
|
81
|
+
"@oclif/plugin-help"
|
|
82
|
+
]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Create Link App
|
|
2
|
+
|
|
3
|
+
This project contains files that form the basis of a new Link App project built with React.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
To start developing your Link App, you can run a local development server on [http://localhost:3000](http://localhost:3000) via:
|
|
8
|
+
|
|
9
|
+
`npm run dev` or `yarn dev`
|
|
10
|
+
|
|
11
|
+
Optionally, you can specify a different port or serve over HTTPS. Run command with `--help` for more information.
|
|
12
|
+
|
|
13
|
+
## File Structure
|
|
14
|
+
|
|
15
|
+
Several files were created during the bootstrapping of this project. Some of these files play a key role:
|
|
16
|
+
|
|
17
|
+
| File | Description |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| `src/index.jsx` | The entry point for your Link App |
|
|
20
|
+
| `manifest.json` | Defines key information about your Link App, and helps users to discover your Link App via the [Linktree Marketplace](https://linktr.ee/marketplace) |
|
|
21
|
+
| `settings.json` | Defines the settings made available to Linktree users when configuring the Link App via [Linktree Admin](https://linktr.ee/admin) |
|
|
22
|
+
| `fixtures/props-data.json` | Used during development, this file acts as mock data for the `settings.json` output, it is injected as props to the entry point component |
|
|
23
|
+
| `url-match-rules.json` | Defines the URL pattern to match against when users add a link on Linktree via URL, its structure follows the [URL Pattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) |
|
|
24
|
+
|
|
25
|
+
## UI Components
|
|
26
|
+
|
|
27
|
+
Linktree provides a set of flexible UI components via `@linktr.ee/ui-link-kit` to build and customize your Link App. You can check out the list of components in Storybook.
|
|
28
|
+
|
|
29
|
+
## Deploying Your Link App
|
|
30
|
+
|
|
31
|
+
When you're ready to test your Link App, you can deploy it in `DRAFT` state and test it on [Linktr.ee](https://linktr.ee). You will require a Linktree account with developer privileges to do this, please contact Linktree if you need access.
|
|
32
|
+
|
|
33
|
+
### Login
|
|
34
|
+
|
|
35
|
+
To initiate the login flow, run the following:
|
|
36
|
+
|
|
37
|
+
`npx @linktr.ee/create-link-app login`
|
|
38
|
+
|
|
39
|
+
This will open a login page in your default browser. Follow the prompts and sign in with your Linktree account credentials.
|
|
40
|
+
|
|
41
|
+
Once login is successful, an access token will be stored locally on your machine under `~/.netrc`. Link App deployments will now use this token to authenticate requests.
|
|
42
|
+
|
|
43
|
+
### Deploy
|
|
44
|
+
|
|
45
|
+
Before deployment, please ensure that the properties in your `manifest.json` and `settings.json` data are accurate and complete.
|
|
46
|
+
|
|
47
|
+
Deploy your Link App via:
|
|
48
|
+
|
|
49
|
+
`npm run deploy` or `yarn deploy`
|
|
50
|
+
|
|
51
|
+
This will push your Link App assets through to our build pipeline, where upon a successful build, your Link App will be published in `DRAFT` state.
|
|
52
|
+
|
|
53
|
+
Being in `DRAFT` will limit the Link App from public use and discovery, but you will be able to add the Link App via your Linktree Admin page and profile to validate its functionality.
|
|
54
|
+
|
|
55
|
+
## Submitting Your Link App for Review
|
|
56
|
+
|
|
57
|
+
Once you're satisfied that your Link App functions as expected, you can request a review from Linktree to have your Link App submitted to the Linktree Marketplace:
|
|
58
|
+
|
|
59
|
+
To submit your Link App for review:
|
|
60
|
+
|
|
61
|
+
**TODO: Submission flow**
|
|
62
|
+
|
|
63
|
+
Linktree will review and test your Link App, requesting changes if necessary. Following approval, your Link App will be put in the `PUBLISHED` state and made available via the Linktree Marketplace for public use.
|
|
64
|
+
|
|
65
|
+
## Granting Access to Additional Developers
|
|
66
|
+
|
|
67
|
+
Once a Link App has been deployed for the first time, the user who deployed it will be set as the `Owner` of the Link App by default. The `Owner` role represents a set of elevated admin-like access privileges granted to the user for actions that can be performed on the Link App. Initially only the `Owner` will have access to retrieve and modify the Link App.
|
|
68
|
+
|
|
69
|
+
Access to a Link App can be granted to additional users who may need to retrieve data or make changes to the Link App. You can grant a `Maintainer` role to a user for your Link App via the `grant-access` command:
|
|
70
|
+
|
|
71
|
+
`npx @linktr.ee/create-link-app grant-access <link_app_id> <username>`
|
|
72
|
+
|
|
73
|
+
Please note the user receiving access must also have a Linktree account with developer privileges. Maintainer access can be granted to as many developer accounts as required.
|
|
74
|
+
|
|
75
|
+
***Note: Only the Link App Owner can grant Maintainer access to additional users. You must be logged in with an Owner account in order to use the command.***
|
|
76
|
+
|
|
77
|
+
### Maintainer and Owner Permitted Actions
|
|
78
|
+
|
|
79
|
+
The following table compares which actions are permitted for Maintainers and Owners:
|
|
80
|
+
|
|
81
|
+
|Action |Owner |Maintainer|Notes |
|
|
82
|
+
|---------------------------|-------|----------|-----------------------------------------------|
|
|
83
|
+
|Create Link App |✓|N/A |Owner is defined when Link App is first created|
|
|
84
|
+
|Get Link App |✓|✓ | |
|
|
85
|
+
|Update Link App |✓|✓ | |
|
|
86
|
+
|Request Link App review |✓|✓ | |
|
|
87
|
+
|Get Link App maintainers |✓|✓ | |
|
|
88
|
+
|Add new Link App maintainer|✓|✗ | |
|
|
89
|
+
|Remove Link App maintainer |✓|✗ | |
|