@linktr.ee/create-link-app 1.5.1 → 1.7.3
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/dist/commands/build.js +1 -0
- package/dist/commands/deploy.js +16 -7
- package/dist/lib/deploy/pack-project.js +22 -15
- package/dist/lib/schema/validate.js +24 -0
- package/dist/schema/manifest.schema.json +121 -0
- package/dist/storybook/main.js +1 -1
- package/dist/webpack/webpack.config.js +3 -0
- package/oclif.manifest.json +8 -1
- package/package.json +1 -1
- package/templates/common/manifest.json +0 -1
- package/templates/stories/LinkApp.stories.tsx +25 -25
package/dist/commands/build.js
CHANGED
package/dist/commands/deploy.js
CHANGED
|
@@ -23,7 +23,8 @@ class Deploy extends base_1.default {
|
|
|
23
23
|
const accessToken = (0, access_token_1.getAccessToken)(appConfig.auth.audience);
|
|
24
24
|
const linkTypeServiceUrl = `${flags.endpoint ?? appConfig.link_types_url}/link-types`;
|
|
25
25
|
const isForceUpdate = !!flags['force-update'];
|
|
26
|
-
const
|
|
26
|
+
const isQa = !!flags.qa;
|
|
27
|
+
const packedFiles = await (0, pack_project_1.default)(flags.path);
|
|
27
28
|
this.log(`\n===`);
|
|
28
29
|
if (isForceUpdate) {
|
|
29
30
|
this.log('⚠️ "--force-update" flag will impact the existing PUBLISHED Link Apps.\n');
|
|
@@ -34,10 +35,12 @@ class Deploy extends base_1.default {
|
|
|
34
35
|
this.log(` - ${file}`);
|
|
35
36
|
});
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
if (!flags['skip-confirm']) {
|
|
39
|
+
const userConfirmDeploy = await core_1.CliUx.ux.prompt(`❓ ${isQa ? '[QA]' : ''} Do you want to proceed? (Y/n)`, { required: false });
|
|
40
|
+
if (userConfirmDeploy?.toLowerCase() !== 'y') {
|
|
41
|
+
this.log('Aborted!');
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
41
44
|
}
|
|
42
45
|
const form = (0, create_form_data_1.default)(flags.path);
|
|
43
46
|
let linkTypeErrorCode = null;
|
|
@@ -93,7 +96,7 @@ class Deploy extends base_1.default {
|
|
|
93
96
|
this.log(`\n===`);
|
|
94
97
|
this.log(JSON.stringify(result, null, 2));
|
|
95
98
|
const linkTypeId = result.linkType.linkTypeId;
|
|
96
|
-
const buildId = result.build
|
|
99
|
+
const buildId = result.build?.id;
|
|
97
100
|
const linkAppBuildUrl = flags.qa
|
|
98
101
|
? `https://buildkite.com/linktree/link-types-ci-service/builds/${buildId}`
|
|
99
102
|
: `https://linkapp-ci.replit.app/?linkTypeId=${linkTypeId}&buildId=${buildId}`;
|
|
@@ -103,7 +106,9 @@ class Deploy extends base_1.default {
|
|
|
103
106
|
this.log(`\n===`);
|
|
104
107
|
this.log(`✅ Draft Link App successfully ${flags.update ? 'updated' : 'uploaded'}`);
|
|
105
108
|
this.log(` - Link App ID: ${linkTypeId}`);
|
|
106
|
-
|
|
109
|
+
if (buildId) {
|
|
110
|
+
this.log(` - Check Link App build: ${linkAppBuildUrl}`);
|
|
111
|
+
}
|
|
107
112
|
this.log(` - Create Link App: ${linkAppCreateUrl}`);
|
|
108
113
|
}
|
|
109
114
|
}
|
|
@@ -130,4 +135,8 @@ Deploy.flags = {
|
|
|
130
135
|
description: 'Allow Link Type Admins to push updates to Link Apps in PUBLISHED state. Admin use only.',
|
|
131
136
|
hidden: true,
|
|
132
137
|
}),
|
|
138
|
+
'skip-confirm': core_1.Flags.boolean({
|
|
139
|
+
description: 'Skip confirmation prompt',
|
|
140
|
+
hidden: true,
|
|
141
|
+
}),
|
|
133
142
|
};
|
|
@@ -32,13 +32,21 @@ const path_1 = __importDefault(require("path"));
|
|
|
32
32
|
/**
|
|
33
33
|
* Pack the Link App source files into `package.tgz`. This includes the above required/optional directories and files.
|
|
34
34
|
*/
|
|
35
|
-
async function packProject() {
|
|
36
|
-
const
|
|
37
|
-
const
|
|
38
|
-
const optionalFiles = [
|
|
35
|
+
async function packProject(givenPath) {
|
|
36
|
+
const requiredFiles = ['manifest.json'];
|
|
37
|
+
const optionalDirs = ['src']; // For legacy LinkApp, it could have no src code.
|
|
38
|
+
const optionalFiles = [
|
|
39
|
+
'package.json',
|
|
40
|
+
'package-lock.json',
|
|
41
|
+
'yarn.lock',
|
|
42
|
+
'tsconfig.json',
|
|
43
|
+
'url_match_rules.json',
|
|
44
|
+
'settings.json',
|
|
45
|
+
'icon.svg',
|
|
46
|
+
];
|
|
39
47
|
const packedFiles = [];
|
|
40
48
|
await new Promise((resolve, reject) => {
|
|
41
|
-
const outputPath = resolveProjPath('package.tgz');
|
|
49
|
+
const outputPath = resolveProjPath(givenPath, 'package.tgz');
|
|
42
50
|
const outputWriteStream = fs_1.default.createWriteStream(outputPath);
|
|
43
51
|
const handleSuccess = () => resolve();
|
|
44
52
|
const handleFailure = (err) => {
|
|
@@ -59,29 +67,28 @@ async function packProject() {
|
|
|
59
67
|
});
|
|
60
68
|
archive.pipe(outputWriteStream);
|
|
61
69
|
requiredFiles.forEach((fileName) => {
|
|
62
|
-
if (!fs_1.default.existsSync(resolveProjPath(fileName))) {
|
|
70
|
+
if (!fs_1.default.existsSync(resolveProjPath(givenPath, fileName))) {
|
|
63
71
|
handleFailure(new archiver_1.ArchiverError(`File ${fileName} does not exist`, 'ENOENT'));
|
|
64
72
|
}
|
|
65
|
-
archive.file(resolveProjPath(fileName), { name: fileName });
|
|
73
|
+
archive.file(resolveProjPath(givenPath, fileName), { name: fileName });
|
|
66
74
|
packedFiles.push(fileName);
|
|
67
75
|
});
|
|
68
76
|
optionalFiles.forEach((fileName) => {
|
|
69
|
-
if (fs_1.default.existsSync(resolveProjPath(fileName))) {
|
|
70
|
-
archive.file(resolveProjPath(fileName), { name: fileName });
|
|
77
|
+
if (fs_1.default.existsSync(resolveProjPath(givenPath, fileName))) {
|
|
78
|
+
archive.file(resolveProjPath(givenPath, fileName), { name: fileName });
|
|
71
79
|
packedFiles.push(fileName);
|
|
72
80
|
}
|
|
73
81
|
});
|
|
74
|
-
|
|
75
|
-
if (
|
|
76
|
-
|
|
82
|
+
optionalDirs.forEach((dirName) => {
|
|
83
|
+
if (fs_1.default.existsSync(resolveProjPath(givenPath, dirName)) && fs_1.default.statSync(resolveProjPath(givenPath, dirName)).isDirectory()) {
|
|
84
|
+
archive.directory(resolveProjPath(givenPath, `${dirName}/`), dirName);
|
|
85
|
+
packedFiles.push(`${dirName}/`);
|
|
77
86
|
}
|
|
78
|
-
archive.directory(resolveProjPath(`${dirName}/`), dirName);
|
|
79
|
-
packedFiles.push(`${dirName}/`);
|
|
80
87
|
});
|
|
81
88
|
archive.finalize();
|
|
82
89
|
});
|
|
83
90
|
packedFiles.sort();
|
|
84
91
|
return packedFiles;
|
|
85
92
|
}
|
|
86
|
-
const resolveProjPath = (relativePath) => path_1.default.resolve(process.cwd(), relativePath);
|
|
93
|
+
const resolveProjPath = (givenPath, relativePath) => path_1.default.resolve(givenPath ?? process.cwd(), relativePath);
|
|
87
94
|
exports.default = packProject;
|
|
@@ -0,0 +1,24 @@
|
|
|
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.validateJSON = void 0;
|
|
7
|
+
const ajv_1 = __importDefault(require("ajv"));
|
|
8
|
+
const ajv = new ajv_1.default();
|
|
9
|
+
/**
|
|
10
|
+
* Validates a JSON object against a given schema.
|
|
11
|
+
* @param data - The JSON data to validate.
|
|
12
|
+
* @param schema - The JSON schema to validate against.
|
|
13
|
+
* @returns True if the data is valid, otherwise false.
|
|
14
|
+
*/
|
|
15
|
+
function validateJSON(filename, data, schema) {
|
|
16
|
+
const validate = ajv.compile(schema);
|
|
17
|
+
const valid = validate(data);
|
|
18
|
+
if (!valid) {
|
|
19
|
+
console.error(`❌ Failed to validate ${filename}`);
|
|
20
|
+
console.error(validate.errors);
|
|
21
|
+
}
|
|
22
|
+
return valid;
|
|
23
|
+
}
|
|
24
|
+
exports.validateJSON = validateJSON;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "Manifest",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"name": {
|
|
7
|
+
"type": "string",
|
|
8
|
+
"description": "The name of the link type.",
|
|
9
|
+
"minLength": 3,
|
|
10
|
+
"maxLength": 50
|
|
11
|
+
},
|
|
12
|
+
"tagline": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"description": "A short tagline for the link type.",
|
|
15
|
+
"minLength": 3,
|
|
16
|
+
"maxLength": 200
|
|
17
|
+
},
|
|
18
|
+
"description": {
|
|
19
|
+
"type": "array",
|
|
20
|
+
"items": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"version": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "The version of the link type.",
|
|
27
|
+
"pattern": "^\\d+\\.\\d+\\.\\d+$"
|
|
28
|
+
},
|
|
29
|
+
"supporting_links": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"properties": {
|
|
32
|
+
"terms_of_service": {
|
|
33
|
+
"type": "string",
|
|
34
|
+
"description": "The URL of the terms of service."
|
|
35
|
+
},
|
|
36
|
+
"privacy_policy": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"description": "The URL of the privacy policy."
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"category": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "The category of the link type.",
|
|
45
|
+
"enum": [
|
|
46
|
+
"grow",
|
|
47
|
+
"share",
|
|
48
|
+
"sell",
|
|
49
|
+
"other"
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
"search_terms": {
|
|
53
|
+
"type": "array",
|
|
54
|
+
"description": "The search terms for the link type.",
|
|
55
|
+
"items": {
|
|
56
|
+
"type": "string"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"url_match_urls": {
|
|
60
|
+
"type": "object",
|
|
61
|
+
"properties": {
|
|
62
|
+
"hostnames": {
|
|
63
|
+
"type": "array",
|
|
64
|
+
"items": {
|
|
65
|
+
"type": "string"
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"patterns": {
|
|
69
|
+
"type": "array",
|
|
70
|
+
"items": {
|
|
71
|
+
"type": "object"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"notPatterns": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"type": "object"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"author": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"properties": {
|
|
85
|
+
"name": {
|
|
86
|
+
"type": "string",
|
|
87
|
+
"description": "The name of the author."
|
|
88
|
+
},
|
|
89
|
+
"accounts": {
|
|
90
|
+
"type": "array",
|
|
91
|
+
"items": {
|
|
92
|
+
"type": "string"
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
"contact": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"url": {
|
|
99
|
+
"type": "string",
|
|
100
|
+
"description": "The URL of the contact."
|
|
101
|
+
},
|
|
102
|
+
"email": {
|
|
103
|
+
"type": "string",
|
|
104
|
+
"description": "The email of the contact."
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"required": [
|
|
110
|
+
"name"
|
|
111
|
+
]
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
"required": [
|
|
115
|
+
"name",
|
|
116
|
+
"tagline",
|
|
117
|
+
"version",
|
|
118
|
+
"author"
|
|
119
|
+
],
|
|
120
|
+
"additionalProperties": true
|
|
121
|
+
}
|
package/dist/storybook/main.js
CHANGED
|
@@ -10,7 +10,7 @@ const postcss_config_1 = require("../postcss/postcss.config");
|
|
|
10
10
|
const linkTypeSlug = 'test-id';
|
|
11
11
|
const linkTypeId = 'testId';
|
|
12
12
|
const config = {
|
|
13
|
-
stories: [`${process.cwd()}/stories
|
|
13
|
+
stories: [`${process.cwd()}/stories/**/*.stories.@(js|jsx|ts|tsx)`],
|
|
14
14
|
addons: [
|
|
15
15
|
'@storybook/addon-links',
|
|
16
16
|
'@storybook/addon-essentials',
|
|
@@ -15,6 +15,8 @@ require("webpack-dev-server");
|
|
|
15
15
|
const camelcase_1 = __importDefault(require("camelcase"));
|
|
16
16
|
const slugify_1 = __importDefault(require("slugify"));
|
|
17
17
|
const compile_1 = __importDefault(require("../lib/schema/compile"));
|
|
18
|
+
const validate_1 = require("../lib/schema/validate");
|
|
19
|
+
const manifest_schema_json_1 = __importDefault(require("../schema/manifest.schema.json"));
|
|
18
20
|
const postcss_config_1 = require("../postcss/postcss.config");
|
|
19
21
|
const { ModuleFederationPlugin } = webpack_1.container;
|
|
20
22
|
function toVirtualModule(code) {
|
|
@@ -39,6 +41,7 @@ async function default_1(env, options) {
|
|
|
39
41
|
const useEditorEntry = hasEntrypoint(appDir, 'Editor.tsx');
|
|
40
42
|
const appEntry = useAppEntry ? path_1.default.resolve(appDir, 'src/App') : path_1.default.resolve(appDir, 'src');
|
|
41
43
|
const manifestJson = JSON.parse(fs_1.default.readFileSync(path_1.default.resolve(appDir, 'manifest.json'), 'utf8'));
|
|
44
|
+
(0, validate_1.validateJSON)('manifest.json', manifestJson, manifest_schema_json_1.default);
|
|
42
45
|
const linkTypeName = manifestJson.name.replace(/[^A-Za-z0-9 ]/g, '');
|
|
43
46
|
const linkTypeSlug = (0, slugify_1.default)(linkTypeName, { lower: true });
|
|
44
47
|
const linkTypeId = (0, camelcase_1.default)(linkTypeSlug, { pascalCase: true });
|
package/oclif.manifest.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.
|
|
2
|
+
"version": "1.7.3",
|
|
3
3
|
"commands": {
|
|
4
4
|
"build": {
|
|
5
5
|
"id": "build",
|
|
@@ -119,6 +119,13 @@
|
|
|
119
119
|
"description": "Allow Link Type Admins to push updates to Link Apps in PUBLISHED state. Admin use only.",
|
|
120
120
|
"hidden": true,
|
|
121
121
|
"allowNo": false
|
|
122
|
+
},
|
|
123
|
+
"skip-confirm": {
|
|
124
|
+
"name": "skip-confirm",
|
|
125
|
+
"type": "boolean",
|
|
126
|
+
"description": "Skip confirmation prompt",
|
|
127
|
+
"hidden": true,
|
|
128
|
+
"allowNo": false
|
|
122
129
|
}
|
|
123
130
|
},
|
|
124
131
|
"args": {}
|
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
"The simple greeting link is an example link type which enables a user to to show a simple greeting on their linktr.ee profile.",
|
|
6
6
|
"This example code acts as a base for experimenting with link type capabilities"
|
|
7
7
|
],
|
|
8
|
-
"manifest_version": "1.0.0",
|
|
9
8
|
"version": "1.0.0",
|
|
10
9
|
"supporting_links": {
|
|
11
10
|
"terms_of_service": "https://example.com/terms-of-service",
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { ComponentMeta, ComponentStory } from
|
|
2
|
-
import React from
|
|
3
|
-
import fixture from
|
|
4
|
-
import manifest from
|
|
1
|
+
import { ComponentMeta, ComponentStory } from '@storybook/react'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import fixture from '../fixtures/props-data.json'
|
|
4
|
+
import manifest from '../manifest.json'
|
|
5
5
|
|
|
6
|
-
import LinkApp from
|
|
6
|
+
import LinkApp from '../src/App'
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import './tailwind.sb.css'
|
|
9
9
|
|
|
10
10
|
export default {
|
|
11
11
|
title: `Link App/${manifest.name}`,
|
|
12
12
|
component: LinkApp,
|
|
13
|
-
tags: [
|
|
13
|
+
tags: ['autodocs'],
|
|
14
14
|
parameters: {
|
|
15
|
-
layout:
|
|
15
|
+
layout: 'fullscreen',
|
|
16
16
|
},
|
|
17
17
|
args: {
|
|
18
18
|
...fixture,
|
|
@@ -25,68 +25,68 @@ export default {
|
|
|
25
25
|
},
|
|
26
26
|
},
|
|
27
27
|
},
|
|
28
|
-
} as ComponentMeta<typeof LinkApp
|
|
28
|
+
} as ComponentMeta<typeof LinkApp>
|
|
29
29
|
|
|
30
30
|
export const Stack: ComponentStory<typeof LinkApp> = (args) => (
|
|
31
31
|
<div
|
|
32
32
|
data-link-type-id={manifest.name}
|
|
33
33
|
style={{
|
|
34
|
-
margin:
|
|
34
|
+
margin: '0.5rem',
|
|
35
35
|
width: 374,
|
|
36
36
|
height: 80,
|
|
37
37
|
}}
|
|
38
38
|
>
|
|
39
39
|
<LinkApp {...args} />
|
|
40
40
|
</div>
|
|
41
|
-
)
|
|
41
|
+
)
|
|
42
42
|
Stack.args = {
|
|
43
|
-
__layout:
|
|
44
|
-
}
|
|
43
|
+
__layout: 'STACK',
|
|
44
|
+
}
|
|
45
45
|
|
|
46
46
|
export const Grid: ComponentStory<typeof LinkApp> = (args) => (
|
|
47
47
|
<div
|
|
48
48
|
data-link-type-id={manifest.name}
|
|
49
49
|
style={{
|
|
50
|
-
margin:
|
|
50
|
+
margin: '0.5rem',
|
|
51
51
|
width: 183,
|
|
52
52
|
height: 240,
|
|
53
53
|
}}
|
|
54
54
|
>
|
|
55
55
|
<LinkApp {...args} />
|
|
56
56
|
</div>
|
|
57
|
-
)
|
|
57
|
+
)
|
|
58
58
|
Grid.args = {
|
|
59
|
-
__layout:
|
|
60
|
-
}
|
|
59
|
+
__layout: 'GRID',
|
|
60
|
+
}
|
|
61
61
|
|
|
62
62
|
export const Carousel: ComponentStory<typeof LinkApp> = (args) => (
|
|
63
63
|
<div
|
|
64
64
|
data-link-type-id={manifest.name}
|
|
65
65
|
style={{
|
|
66
|
-
margin:
|
|
66
|
+
margin: '0.5rem',
|
|
67
67
|
width: 224,
|
|
68
68
|
height: 296,
|
|
69
69
|
}}
|
|
70
70
|
>
|
|
71
71
|
<LinkApp {...args} />
|
|
72
72
|
</div>
|
|
73
|
-
)
|
|
73
|
+
)
|
|
74
74
|
Carousel.args = {
|
|
75
|
-
__layout:
|
|
76
|
-
}
|
|
75
|
+
__layout: 'CAROUSEL',
|
|
76
|
+
}
|
|
77
77
|
|
|
78
78
|
export const Featured: ComponentStory<typeof LinkApp> = (args) => (
|
|
79
79
|
<div
|
|
80
80
|
data-link-type-id={manifest.name}
|
|
81
81
|
style={{
|
|
82
|
-
margin:
|
|
82
|
+
margin: '0.5rem',
|
|
83
83
|
width: 374,
|
|
84
84
|
height: 374,
|
|
85
85
|
}}
|
|
86
86
|
>
|
|
87
87
|
<LinkApp {...args} />
|
|
88
88
|
</div>
|
|
89
|
-
)
|
|
89
|
+
)
|
|
90
90
|
Featured.args = {
|
|
91
|
-
__layout:
|
|
92
|
-
}
|
|
91
|
+
__layout: 'FEATURED',
|
|
92
|
+
}
|