├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
└── FUNDING.yml
├── .gitignore
├── .lintstagedrc.yml
├── .prettierrc.json
├── README.md
├── api
├── _utils
│ ├── extract-http-error.ts
│ ├── http_status_code.ts
│ └── pkg.ts
├── ping.ts
├── pkg-from-url.ts
└── pkg.ts
├── docs
├── .vuepress
│ ├── .eslintrc.js
│ ├── .lintstagedrc.yml
│ ├── .npmignore
│ ├── api-from-url.js
│ ├── components
│ │ ├── GitpkgApi.vue
│ │ └── ServiceWorkerPopup.vue
│ ├── config.js
│ ├── enhanceApp.js
│ ├── gen
│ │ └── site-meta.json
│ ├── install-command.js
│ ├── my-components
│ │ ├── ActionBar.vue
│ │ ├── ApiChoicesDisplay.vue
│ │ ├── ButtonGroup.vue
│ │ ├── CopyText.vue
│ │ ├── CustomScriptEdit.vue
│ │ ├── CustomScripts.vue
│ │ ├── PmIcon.vue
│ │ ├── SelectDropdown.vue
│ │ └── SingleApiDisplay.vue
│ ├── package.json
│ ├── public
│ │ ├── cover.svg
│ │ ├── favicon.svg
│ │ ├── icon.svg
│ │ ├── icons
│ │ │ ├── apple-icon-120.png
│ │ │ ├── apple-icon-152.png
│ │ │ ├── apple-icon-167.png
│ │ │ ├── apple-icon-180.png
│ │ │ ├── favicon-196.png
│ │ │ ├── manifest-icon-192.png
│ │ │ └── manifest-icon-512.png
│ │ └── manifest.webmanifest
│ ├── scripts
│ │ ├── constants.js
│ │ ├── gen-assets.js
│ │ ├── main.js
│ │ └── manifest.webmanifest
│ ├── styles
│ │ ├── index.styl
│ │ ├── palette.styl
│ │ └── select.styl
│ └── yarn-svg-content.js
├── README.md
└── guide
│ └── README.md
├── lerna.json
├── now.json
├── package.json
├── packages
└── core
│ ├── .eslintignore
│ ├── .eslintrc.js
│ ├── .lintstagedrc.yml
│ ├── babel.config.js
│ ├── jest.config.js
│ ├── package.json
│ ├── src
│ ├── api
│ │ ├── codeload-url.ts
│ │ ├── download-git-pkg.ts
│ │ └── index.ts
│ ├── index.ts
│ ├── parse-url-query
│ │ ├── default-parser.ts
│ │ ├── error.ts
│ │ ├── get-value.ts
│ │ ├── index.ts
│ │ ├── parser.ts
│ │ └── plugins
│ │ │ ├── custom-scripts
│ │ │ ├── index.ts
│ │ │ ├── parse-query.spec.ts
│ │ │ ├── parse-query.ts
│ │ │ └── plugin.ts
│ │ │ ├── index.ts
│ │ │ └── url-and-commit
│ │ │ ├── commit-ish.ts
│ │ │ ├── from-query.ts
│ │ │ ├── from-url.ts
│ │ │ ├── index.ts
│ │ │ └── plugin.ts
│ └── tar
│ │ ├── custom-scripts.spec.ts
│ │ ├── custom-scripts.ts
│ │ ├── extract-sub-folder.spec.ts
│ │ ├── extract-sub-folder.ts
│ │ ├── index.ts
│ │ ├── modify-single-file.ts
│ │ ├── prepend-path.spec.ts
│ │ └── prepend-path.ts
│ ├── test
│ └── util
│ │ └── tar-entry.ts
│ ├── tsconfig.json
│ └── tsconfig.prod.json
├── tools
└── common
│ ├── .eslintrc.js
│ ├── .lintstagedrc.yml
│ ├── babel
│ └── index.js
│ ├── eslint
│ ├── index.js
│ └── node.js
│ ├── jest
│ └── index.js
│ ├── package.json
│ └── tsconfig.json
├── tsconfig.json
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = space
5 | indent_size = 2
6 | end_of_line = lf
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /.now
2 | /public
3 | /packages
4 | /tools
5 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require("@gitpkg/common/eslint")(__dirname);
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: gitpkg
6 | ko_fi: equalma
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .eslintcache
2 | .now
3 | /public
4 | dist
5 |
6 | .DS_Store
7 | node_modules
8 | /dist
9 |
10 | # local env files
11 | .env.local
12 | .env.*.local
13 | .env
14 | .env.build
15 |
16 | # Log files
17 | npm-debug.log*
18 | yarn-debug.log*
19 | yarn-error.log*
20 |
21 | # Editor directories and files
22 | .idea
23 | .vscode
24 | *.suo
25 | *.ntvs*
26 | *.njsproj
27 | *.sln
28 | *.sw?
29 |
--------------------------------------------------------------------------------
/.lintstagedrc.yml:
--------------------------------------------------------------------------------
1 | "*.{md,html,css,yml,yaml,json}": "prettier --write"
2 | ".eslintrc.js": "prettier --write"
3 | "./!(.eslintrc).{js,ts}": "yarn run lint:fix"
4 | "api/*.{js,ts}": "yarn run lint:fix"
5 | "*.{png,jpeg,jpg,gif,svg}": "imagemin-lint-staged"
6 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "all"
3 | }
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # GitPkg
4 |
5 | [](https://gitpkg.now.sh)
6 | [](https://lerna.js.org/)
7 |
8 | GitPkg enables you to use a sub directory in a github repo as yarn / npm dependency.
9 |
10 | [:tada: Try Now !](https://gitpkg.now.sh)
11 |
12 | :unicorn: Features:
13 |
14 | - sub folder of a github repo as yarn / npm dependency
15 | - use [custom scripts](https://gitpkg.now.sh/guide/#custom-scripts) to build source code when installing
16 |
--------------------------------------------------------------------------------
/api/_utils/extract-http-error.ts:
--------------------------------------------------------------------------------
1 | import { HTTPError } from "got";
2 |
3 | export interface HttpErrorInfo {
4 | code: number;
5 | message: string;
6 | }
7 |
8 | export function extractInfoFromHttpError(
9 | err: unknown,
10 | defaults: HttpErrorInfo,
11 | ) {
12 | if (err instanceof HTTPError) {
13 | const code =
14 | (err.code && parseInt(err.code)) ||
15 | err.response.statusCode ||
16 | defaults.code;
17 | const message = err.message;
18 | return { code, message };
19 | } else return defaults;
20 | }
21 |
--------------------------------------------------------------------------------
/api/_utils/http_status_code.ts:
--------------------------------------------------------------------------------
1 | export const BAD_REQUEST = 400;
2 | export const INTERNAL_SERVER_ERROR = 500;
3 |
--------------------------------------------------------------------------------
/api/_utils/pkg.ts:
--------------------------------------------------------------------------------
1 | import * as codes from "./http_status_code";
2 | import { pipelineToDownloadGitPkg, getDefaultParser } from "@gitpkg/core";
3 | import * as stream from "stream";
4 | import { promisify } from "util";
5 | import { extractInfoFromHttpError } from "./extract-http-error";
6 |
7 | const pipeline = promisify(stream.pipeline);
8 |
9 | export interface PkgToResponseOptions {
10 | requestUrl: string | undefined;
11 | query: import("@now/node").NowRequestQuery;
12 | parseFromUrl: boolean;
13 | response: import("@now/node").NowResponse;
14 | }
15 |
16 | export async function pkg({
17 | requestUrl,
18 | query,
19 | parseFromUrl,
20 | response,
21 | }: PkgToResponseOptions) {
22 | try {
23 | const pkgOpts = getDefaultParser(parseFromUrl).parse(
24 | requestUrl || "",
25 | query,
26 | );
27 | const { commitIshInfo: cii } = pkgOpts;
28 |
29 | response.status(200);
30 | response.setHeader(
31 | "Content-Disposition",
32 | `attachment; filename="${[
33 | cii.user,
34 | cii.repo,
35 | ...(cii.subdirs || []),
36 | cii.commit,
37 | ]
38 | .filter(Boolean)
39 | .join("-")}.tgz"`,
40 | );
41 | response.setHeader("Content-Type", "application/gzip");
42 | await pipeline([...pipelineToDownloadGitPkg(pkgOpts), response]);
43 | } catch (err) {
44 | console.error(`request ${requestUrl} fail with message: ${err.message}`);
45 | const { code, message } = extractInfoFromHttpError(err, {
46 | code: codes.INTERNAL_SERVER_ERROR,
47 | message: `download or parse fail for: ${requestUrl}`,
48 | });
49 | response.removeHeader("Content-Disposition");
50 | response.removeHeader("Content-Type");
51 | response.status(code).json(message);
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/api/ping.ts:
--------------------------------------------------------------------------------
1 | import { NowRequest, NowResponse } from "@now/node";
2 |
3 | export default (request: NowRequest, response: NowResponse) => {
4 | const { name = "World" } = request.query;
5 | response.status(200).json({
6 | msg: `Hello ${name} at timestamp ${new Date().getTime()}`,
7 | query: request.query,
8 | url: request.url,
9 | });
10 | };
11 |
--------------------------------------------------------------------------------
/api/pkg-from-url.ts:
--------------------------------------------------------------------------------
1 | import { NowRequest, NowResponse } from "@now/node";
2 | import { pkg } from "./_utils/pkg";
3 |
4 | export default async (request: NowRequest, response: NowResponse) => {
5 | await pkg({
6 | query: request.query,
7 | requestUrl: request.url,
8 | parseFromUrl: true,
9 | response,
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/api/pkg.ts:
--------------------------------------------------------------------------------
1 | import { NowRequest, NowResponse } from "@now/node";
2 | import { pkg } from "./_utils/pkg";
3 |
4 | export default async (request: NowRequest, response: NowResponse) => {
5 | await pkg({
6 | query: request.query,
7 | requestUrl: request.url,
8 | parseFromUrl: false,
9 | response,
10 | });
11 | };
12 |
--------------------------------------------------------------------------------
/docs/.vuepress/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: [
4 | "eslint:recommended",
5 | "plugin:prettier/recommended",
6 | "plugin:node/recommended-module",
7 | ],
8 | parserOptions: {
9 | ecmaVersion: 2020,
10 | },
11 | overrides: [
12 | {
13 | files: ["./config.js", "scripts/**/*.js"],
14 | extends: [
15 | "eslint:recommended",
16 | "plugin:prettier/recommended",
17 | "plugin:node/recommended-script",
18 | ],
19 | rules: {
20 | "node/no-extraneous-require": [
21 | "error",
22 | {
23 | allowModules: ["rimraf"],
24 | },
25 | ],
26 | },
27 | },
28 | ],
29 | };
30 |
--------------------------------------------------------------------------------
/docs/.vuepress/.lintstagedrc.yml:
--------------------------------------------------------------------------------
1 | "*.vue": "prettier --write"
2 | "!(.eslintrc).js": "yarn run lint:fix"
3 |
--------------------------------------------------------------------------------
/docs/.vuepress/.npmignore:
--------------------------------------------------------------------------------
1 | # This file is used to disable
2 | # https://github.com/mysticatea/eslint-plugin-node/blob/HEAD/docs/rules/no-unpublished-require.md
3 | # for build scripts
4 |
5 | /config.js
6 | /scripts
7 |
--------------------------------------------------------------------------------
/docs/.vuepress/api-from-url.js:
--------------------------------------------------------------------------------
1 | const branchNamePrecedence = [
2 | "master",
3 | "dev",
4 | "next",
5 | // tag vx.x.x
6 | name => /[vV][0-9.]+.*/.test(name),
7 | "bug/",
8 | "feat/",
9 | "fix/",
10 | ];
11 |
12 | const getPrecedence = ({ commit }) => {
13 | let i = branchNamePrecedence.findIndex(v =>
14 | typeof v === "function" ? v(commit) : commit === v,
15 | );
16 |
17 | if (i === -1) {
18 | i = branchNamePrecedence.findIndex(
19 | v => typeof v === "string" && commit.startsWith(v),
20 | );
21 | if (i !== -1) i += 10000;
22 | }
23 |
24 | return i === -1 ? Infinity : i;
25 | };
26 |
27 | const API_BASE = "https://gitpkg.now.sh/";
28 | const REGEX_URL = /^https?:\/\/([^/?#]+)\/([^/?#]+)\/([^/?#]+)(?:(?:\/tree\/([^#?]+))|\/)?([#?].*)?$/;
29 |
30 | function customScriptToQueryParam(customScript) {
31 | const { script, name, type } = customScript;
32 |
33 | const r = /(^\s*&&\s*)|(\s*&&\s*$)/g;
34 | let normScript = script.replace(r, "");
35 | switch (type) {
36 | case "prepend":
37 | normScript = normScript + " &&";
38 | break;
39 | case "append":
40 | normScript = "&& " + normScript;
41 | break;
42 | }
43 |
44 | return (
45 | "scripts." + encodeURIComponent(name) + "=" + encodeURIComponent(normScript)
46 | );
47 | }
48 |
49 | function queryStringOf(commit, customScripts) {
50 | // postinstall=echo%20gitpkg&build=echo%20building
51 | const csPart = customScripts
52 | .map(cs => customScriptToQueryParam(cs))
53 | .join("&");
54 |
55 | if (!csPart) return commit ? "?" + commit : "";
56 | else return "?" + (commit || "master") + "&" + csPart;
57 | }
58 |
59 | function apiFromCommitInfo(
60 | { commit, subdir, originalUrl, domain, userName, repoName },
61 | customScripts,
62 | ) {
63 | customScripts = customScripts.filter(cs => cs.name && cs.script);
64 |
65 | const repo = userName + "/" + repoName;
66 |
67 | const data = {
68 | originalUrl,
69 | domain,
70 | userName,
71 | repoName,
72 | commit,
73 | subdir,
74 | };
75 |
76 | const commitPart = queryStringOf(commit, customScripts);
77 |
78 | if (!subdir) {
79 | if (customScripts.length === 0) {
80 | return {
81 | type: "warn",
82 | warnType: "suggest-to-use",
83 | data,
84 | suggestion: {
85 | apiUrl: commit ? repo + "#" + commit : repo,
86 | },
87 | apiUrl: API_BASE + repo + commitPart,
88 | params: { url: repo, commit },
89 | };
90 | } else {
91 | return {
92 | type: "success",
93 | data,
94 | apiUrl: API_BASE + repo + commitPart,
95 | params: { url: repo, commit },
96 | };
97 | }
98 | } else {
99 | return {
100 | type: "success",
101 | data,
102 | apiUrl: API_BASE + repo + "/" + subdir + commitPart,
103 | params: { url: repo + "/" + subdir, commit },
104 | };
105 | }
106 | }
107 |
108 | export const apiFromUrl = (url, customScripts) => {
109 | const match = REGEX_URL.exec(url.trim());
110 | if (!match) {
111 | return {
112 | type: "error",
113 | errorType: "url-wrong",
114 | data: {
115 | originalUrl: url,
116 | },
117 | };
118 | }
119 |
120 | const [fullUrl, domain, userName, repoName, _commitAndSubDir] = match;
121 |
122 | const data = {
123 | originalUrl: fullUrl,
124 | domain,
125 | userName,
126 | repoName,
127 | };
128 |
129 | if (domain !== "github.com") {
130 | return {
131 | type: "error",
132 | errorType: "platform-not-supported",
133 | data,
134 | };
135 | }
136 |
137 | const commitAndSubDir =
138 | _commitAndSubDir && _commitAndSubDir.endsWith("/")
139 | ? _commitAndSubDir.slice(0, -1)
140 | : _commitAndSubDir;
141 |
142 | // no sub folder
143 | // || commitAndSubDir.indexOf("/") === -1
144 | if (!commitAndSubDir) {
145 | return apiFromCommitInfo(
146 | {
147 | ...data,
148 | commit: undefined,
149 | subdir: undefined,
150 | },
151 | customScripts,
152 | );
153 | }
154 |
155 | const routes = commitAndSubDir.split("/").filter(Boolean);
156 |
157 | const possibleCommitAndSubDirs = routes.map((p, i, arr) => {
158 | return {
159 | commit: arr.slice(0, i + 1),
160 | subdir: arr.slice(i + 1),
161 | };
162 | });
163 |
164 | return {
165 | type: "choice",
166 | data,
167 | possibleApis: possibleCommitAndSubDirs
168 | .map(p => {
169 | const subdir = p.subdir.join("/");
170 | const commit = p.commit.join("/");
171 | return apiFromCommitInfo({ ...data, subdir, commit }, customScripts);
172 | })
173 | .map(info => ({
174 | info,
175 | precedence: getPrecedence({
176 | commit: info.data.commit,
177 | subdir: info.data.subdir,
178 | }),
179 | }))
180 | .sort((a, b) => a.precedence - b.precedence)
181 | .map(a => a.info),
182 | };
183 | };
184 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/GitpkgApi.vue:
--------------------------------------------------------------------------------
1 |
2 |
43 |
44 |
45 |
84 |
85 |
101 |
--------------------------------------------------------------------------------
/docs/.vuepress/components/ServiceWorkerPopup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
9 |
16 |
42 |
--------------------------------------------------------------------------------
/docs/.vuepress/config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { SITE_META_FILE, PATH_DEST } = require("./scripts/constants");
4 | const fs = require("fs").promises;
5 | const promiseGenerated = fs
6 | .readFile(SITE_META_FILE, "utf-8")
7 | .then(str => JSON.parse(str));
8 |
9 | module.exports = async () => {
10 | const { head, name, description } = await promiseGenerated;
11 |
12 | return {
13 | title: name,
14 | description,
15 | themeConfig: {
16 | nav: [
17 | { text: "Home", link: "/" },
18 | { text: "Guide", link: "/guide/" },
19 | { text: "GitHub", link: "https://github.com/EqualMa/gitpkg" },
20 | ],
21 | },
22 | dest: PATH_DEST,
23 | head: [
24 | ...head,
25 | ["link", { rel: "manifest", href: "/manifest.webmanifest" }],
26 | ["meta", { name: "theme-color", content: "#F06292" }],
27 | [
28 | "meta",
29 | { name: "apple-mobile-web-app-status-bar-style", content: "default" },
30 | ],
31 | ["link", { rel: "mask-icon", href: "/favicon.svg", color: "#ffffff" }],
32 | ["meta", { name: "msapplication-TileImage", content: "/icon.svg" }],
33 | ["meta", { name: "msapplication-TileColor", content: "#F06292" }],
34 | ].filter(Boolean),
35 | plugins: [
36 | [
37 | // https://vuepress.vuejs.org/plugin/official/plugin-pwa.html#vuepress-plugin-pwa
38 | "@vuepress/pwa",
39 | {
40 | serviceWorker: true,
41 | popupComponent: "ServiceWorkerPopup",
42 | updatePopup: true,
43 | generateSWConfig: {
44 | importWorkboxFrom: "local",
45 | },
46 | },
47 | ],
48 | ].filter(Boolean),
49 | };
50 | };
51 |
--------------------------------------------------------------------------------
/docs/.vuepress/enhanceApp.js:
--------------------------------------------------------------------------------
1 | import "normalize.css/normalize.css";
2 | import VueClipboard from "vue-clipboard2";
3 |
4 | // https://vuepress.vuejs.org/guide/basic-config.html#app-level-enhancements
5 | export default ({ Vue }) => {
6 | Vue.use(VueClipboard);
7 | };
8 |
--------------------------------------------------------------------------------
/docs/.vuepress/gen/site-meta.json:
--------------------------------------------------------------------------------
1 | {
2 | "head": [
3 | [
4 | "link",
5 | {
6 | "rel": "icon",
7 | "type": "image/png",
8 | "sizes": "196x196",
9 | "href": "icons/favicon-196.png"
10 | }
11 | ],
12 | ["meta", { "name": "apple-mobile-web-app-capable", "content": "yes" }],
13 | [
14 | "link",
15 | {
16 | "rel": "apple-touch-icon",
17 | "sizes": "180x180",
18 | "href": "icons/apple-icon-180.png"
19 | }
20 | ],
21 | [
22 | "link",
23 | {
24 | "rel": "apple-touch-icon",
25 | "sizes": "167x167",
26 | "href": "icons/apple-icon-167.png"
27 | }
28 | ],
29 | [
30 | "link",
31 | {
32 | "rel": "apple-touch-icon",
33 | "sizes": "152x152",
34 | "href": "icons/apple-icon-152.png"
35 | }
36 | ],
37 | [
38 | "link",
39 | {
40 | "rel": "apple-touch-icon",
41 | "sizes": "120x120",
42 | "href": "icons/apple-icon-120.png"
43 | }
44 | ]
45 | ],
46 | "name": "GitPkg",
47 | "description": "Using sub folders of a repo as yarn/npm dependencies made easy"
48 | }
49 |
--------------------------------------------------------------------------------
/docs/.vuepress/install-command.js:
--------------------------------------------------------------------------------
1 | const managerAndCommands = {
2 | yarn: "yarn add",
3 | npm: "npm install",
4 | };
5 |
6 | // yarn
7 | // https://classic.yarnpkg.com/en/docs/cli/add/
8 | // npm
9 | // https://docs.npmjs.com/cli/install#synopsis
10 | const dependencyTypesAndArgs = {
11 | dependency: "",
12 | dev: "-D",
13 | peer: {
14 | yarn: "-P",
15 | npm: ({ url }) => `"{your-package-name}": ${JSON.stringify(url)}`,
16 | },
17 | optional: "-O",
18 | // exact: "-E",
19 | // tilde: "-T",
20 | bundle: { npm: "-B" },
21 | };
22 |
23 | const managerNames = Object.keys(managerAndCommands);
24 | const dependencyTypes = Object.keys(dependencyTypesAndArgs);
25 |
26 | function getInstallCommand(managerName, dependencyType, url) {
27 | const command = managerAndCommands[managerName];
28 |
29 | let depTypeArg = dependencyTypesAndArgs[dependencyType];
30 |
31 | if (typeof depTypeArg === "object" && depTypeArg !== null) {
32 | depTypeArg = depTypeArg[managerName];
33 | }
34 |
35 | if (typeof depTypeArg === "string") {
36 | return [command, depTypeArg, "'" + url + "'"].filter(Boolean).join(" ");
37 | } else if (typeof depTypeArg === "function") {
38 | return depTypeArg({ managerName, dependencyType, url });
39 | } else if (typeof depTypeArg === "undefined" || depTypeArg === null) {
40 | return "";
41 | }
42 |
43 | throw new Error(
44 | `Arg definition wrong for [Manager=${managerName} & DependencyType=${dependencyType}`,
45 | );
46 | }
47 |
48 | export function installCommands(url) {
49 | const commands = Object.assign(
50 | {},
51 | ...managerNames.map(m => ({
52 | [m]: Object.assign(
53 | {},
54 | ...dependencyTypes.map(d => ({
55 | [d]: getInstallCommand(m, d, url),
56 | })),
57 | ),
58 | })),
59 | );
60 |
61 | return { managerNames, dependencyTypes, commands };
62 | }
63 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/ActionBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
14 |
15 |
16 |
17 |
18 |
19 |
31 |
32 |
62 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/ApiChoicesDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Select the correct commit name
6 |
7 |
8 |
9 |
10 |
16 |
17 | You select commit {{ selected.data.commit }}
, {{ selected.data.subdir ? "" : "no" }} sub folder
19 | {{ selected.data.subdir }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/ButtonGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 | {{ e.label }}
12 |
19 |
20 |
21 |
22 |
41 |
73 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/CopyText.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
20 |
31 |
32 |
33 |
34 |
35 |
36 |
74 |
75 |
79 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/CustomScriptEdit.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
18 |
24 |
30 |
31 |
32 |
33 |
34 |
35 |
61 |
62 |
74 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/CustomScripts.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Custom Scripts
8 |
9 |
15 |
16 |
17 |
23 | Add a custom script
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
77 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/PmIcon.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
20 |
25 |
26 |
27 | {{ name }}
28 |
29 |
64 |
65 |
71 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/SelectDropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
30 |
--------------------------------------------------------------------------------
/docs/.vuepress/my-components/SingleApiDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Only github.com is supported currently
5 |
6 |
7 |
8 |
9 |
10 | Select dependency type
11 |
12 |
13 |
14 |
15 |
21 |
22 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | It seems that no sub folder or custom scripts are specified so you don't
35 | need to use GitPkg.
36 |
37 |
43 | {{
44 | showSuggested
45 | ? "See GitPkg API (not recommended)"
46 | : "Show recommended commands"
47 | }}
48 |
49 |
50 |
51 |
52 |
53 | If you use windows, errors may occur when running
54 | npm install ...
or yarn add ...
55 | because the url contains
56 | {{ "&" }}
57 | . In such cases, you will have to add
58 |
59 |
60 |
61 |
62 | to "dependencies / devDependencies" of your package.json and run
63 | npm install
or yarn install
64 | manually to install the dependency
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
127 |
128 |
135 |
--------------------------------------------------------------------------------
/docs/.vuepress/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "docs",
3 | "version": "1.0.0",
4 | "license": "MIT",
5 | "private": true,
6 | "engines": {
7 | "node": ">=12"
8 | },
9 | "dependencies": {
10 | "mdi-vue": "^1.4.3",
11 | "normalize.css": "^8.0.1",
12 | "vue-clipboard2": "^0.3.1",
13 | "vue-select": "^3.9.5"
14 | },
15 | "devDependencies": {
16 | "@vuepress/plugin-pwa": "^1.4.0",
17 | "parse5": "^5.1.1",
18 | "pwa-asset-generator": "^2.2.1",
19 | "vuepress": "^1.4.0"
20 | },
21 | "scripts": {
22 | "precommit": "lint-staged",
23 | "lint": "eslint",
24 | "lint:fix": "eslint --cache --max-warnings 0 --fix",
25 | "gen-assets": "node scripts/main.js",
26 | "build": "yarn run docs:build",
27 | "docs:dev": "vuepress dev ../",
28 | "docs:build": "vuepress build ../"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/cover.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/favicon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/apple-icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vercel/gitpkg/17394f4162651a4f77f94f816230b2202dd0a9e6/docs/.vuepress/public/icons/apple-icon-120.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/apple-icon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vercel/gitpkg/17394f4162651a4f77f94f816230b2202dd0a9e6/docs/.vuepress/public/icons/apple-icon-152.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/apple-icon-167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vercel/gitpkg/17394f4162651a4f77f94f816230b2202dd0a9e6/docs/.vuepress/public/icons/apple-icon-167.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/apple-icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vercel/gitpkg/17394f4162651a4f77f94f816230b2202dd0a9e6/docs/.vuepress/public/icons/apple-icon-180.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/favicon-196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vercel/gitpkg/17394f4162651a4f77f94f816230b2202dd0a9e6/docs/.vuepress/public/icons/favicon-196.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/manifest-icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vercel/gitpkg/17394f4162651a4f77f94f816230b2202dd0a9e6/docs/.vuepress/public/icons/manifest-icon-192.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/icons/manifest-icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vercel/gitpkg/17394f4162651a4f77f94f816230b2202dd0a9e6/docs/.vuepress/public/icons/manifest-icon-512.png
--------------------------------------------------------------------------------
/docs/.vuepress/public/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GitPkg",
3 | "short_name": "GitPkg",
4 | "description": "Using sub folders of a repo as yarn/npm dependencies made easy",
5 | "background_color": "white",
6 | "categories": [
7 | "utilities",
8 | "dev"
9 | ],
10 | "display": "standalone",
11 | "orientation": "portrait-primary",
12 | "scope": "./",
13 | "theme_color": "#F06292",
14 | "start_url": "./",
15 | "icons": [
16 | {
17 | "src": "icons/manifest-icon-192.png",
18 | "sizes": "192x192",
19 | "type": "image/png"
20 | },
21 | {
22 | "src": "icons/manifest-icon-512.png",
23 | "sizes": "512x512",
24 | "type": "image/png"
25 | }
26 | ]
27 | }
--------------------------------------------------------------------------------
/docs/.vuepress/scripts/constants.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const path = require("path");
4 | exports.SITE_META_FILE = path.join(__dirname, "../gen/site-meta.json");
5 |
6 | exports.PATH_DEST = path.join(__dirname, "../../../public");
7 |
--------------------------------------------------------------------------------
/docs/.vuepress/scripts/gen-assets.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const { generateImages } = require("pwa-asset-generator");
4 | const path = require("path");
5 | const fs = require("fs").promises;
6 | const rimraf = require("rimraf");
7 | const parse5 = require("parse5");
8 |
9 | const PATH_PUBLIC = path.join(__dirname, "../docs/.vuepress/public/");
10 | const PATH_BASE_MANIFEST = path.join(__dirname, "manifest.webmanifest");
11 | const PATH_MANIFEST = path.join(PATH_PUBLIC, "manifest.webmanifest");
12 | const PATH_ICON = path.join(PATH_PUBLIC, "icon.svg");
13 | const PATH_FAVICON = path.join(PATH_PUBLIC, "favicon.svg");
14 |
15 | const PATH_GEN_ICONS = path.join(PATH_PUBLIC, "icons/");
16 |
17 | const BACKGROUND =
18 | "radial-gradient(circle, rgba(225,174,238,1) 0%, rgba(238,174,202,1) 100%)";
19 |
20 | const GEN_ASSETS_OPTIONS = {
21 | scrape: true,
22 | log: false,
23 | type: "png",
24 | };
25 |
26 | function relative(from, to) {
27 | return path
28 | .relative(from, to)
29 | .split(path.sep)
30 | .join("/");
31 | }
32 |
33 | function resetDir(dir) {
34 | return new Promise((resolve, reject) => {
35 | rimraf(dir, err => {
36 | if (err) reject(err);
37 | else resolve();
38 | });
39 | }).then(() => fs.mkdir(dir, { recursive: true }));
40 | }
41 |
42 | function parseXml(xml) {
43 | const { childNodes } = parse5.parseFragment(xml);
44 | return childNodes
45 | .map(c => {
46 | if (c.nodeName === "#text") return null;
47 | return [
48 | c.tagName,
49 | Object.assign(
50 | {},
51 | ...c.attrs.map(({ name, value }) => ({ [name]: value })),
52 | ),
53 | ];
54 | })
55 | .filter(Boolean);
56 | }
57 |
58 | exports.generateAssets = async ({ splashScreen = true }) => {
59 | const [baseManifest] = await Promise.all([
60 | fs.readFile(PATH_BASE_MANIFEST, "utf-8").then(text => {
61 | fs.writeFile(PATH_MANIFEST, text);
62 | return JSON.parse(text);
63 | }),
64 | resetDir(PATH_GEN_ICONS),
65 | ]);
66 |
67 | const { htmlMeta: htmlMetaIcon } = await generateImages(
68 | PATH_FAVICON,
69 | PATH_GEN_ICONS,
70 | {
71 | ...GEN_ASSETS_OPTIONS,
72 | opaque: false,
73 | favicon: true,
74 | iconOnly: true,
75 | log: false,
76 | type: "png",
77 | },
78 | );
79 |
80 | const { htmlMeta } = await generateImages(PATH_ICON, PATH_GEN_ICONS, {
81 | ...GEN_ASSETS_OPTIONS,
82 | background: BACKGROUND,
83 | padding: "0",
84 | manifest: PATH_MANIFEST,
85 | iconOnly: !splashScreen,
86 | });
87 |
88 | const headStr =
89 | htmlMetaIcon.favicon +
90 | Object.keys(htmlMeta)
91 | .map(k => htmlMeta[k])
92 | .join("");
93 |
94 | const manifestDir = path.dirname(PATH_MANIFEST);
95 |
96 | const head = parseXml(headStr).map(([tag, attrs, ...args]) => {
97 | if (attrs.href) {
98 | attrs = {
99 | ...attrs,
100 | href: relative(manifestDir, attrs.href),
101 | };
102 | }
103 |
104 | return [tag, attrs, ...args];
105 | });
106 |
107 | return {
108 | head,
109 | name: baseManifest.name,
110 | description: baseManifest.description,
111 | };
112 | };
113 |
--------------------------------------------------------------------------------
/docs/.vuepress/scripts/main.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | const fs = require("fs").promises;
4 | const { generateAssets } = require("./gen-assets");
5 | const { SITE_META_FILE } = require("./constants");
6 |
7 | const main = async () => {
8 | const res = await generateAssets({ splashScreen: false });
9 | fs.writeFile(SITE_META_FILE, JSON.stringify(res));
10 | };
11 |
12 | main();
13 |
--------------------------------------------------------------------------------
/docs/.vuepress/scripts/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "GitPkg",
3 | "short_name": "GitPkg",
4 | "description": "Using sub folders of a repo as yarn/npm dependencies made easy",
5 | "background_color": "white",
6 | "categories": ["utilities", "dev"],
7 | "display": "standalone",
8 | "orientation": "portrait-primary",
9 | "scope": "./",
10 | "theme_color": "#F06292",
11 | "start_url": "./"
12 | }
13 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/index.styl:
--------------------------------------------------------------------------------
1 | pkg-outline()
2 | border-color transparent
3 | outline-style dashed
4 | outline-offset -3px
5 | outline-width 3px
6 |
7 | pkg-border(c)
8 | border-style solid
9 | border-width: w = 1px
10 | border-color c
11 | border-radius 0
12 |
13 | &.with-left
14 | border-left-width 0px
15 |
16 | &.with-right
17 | border-right-width 0px
18 |
19 | &+.with-left
20 | border-left-width 1px
21 |
22 | $trans = all ease 0.2s 0s, box-shadow ease-out 0.4s 0s
23 |
24 | *
25 | transition $trans
26 |
27 | pkg-focus()
28 | &:focus
29 | box-shadow: 0.5em 0.5em 1em #2c3e506e;
30 | pkg-outline()
31 | outline-color $primaryColor
32 | outline-style solid
33 | outline-width 1px
34 | outline-offset -1px
35 |
36 | pkg-hover()
37 | &:hover
38 | pkg-outline()
39 | box-shadow: 0.5em 0.5em 2em #2c3e506e;
40 | outline-color $accentColor
41 |
42 | .gitpkg-input
43 | c($color)
44 | pkg-outline()
45 | outline-color $color
46 |
47 | box-sizing border-box
48 | font-family source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace
49 | padding 0 1em
50 | height $unitSize
51 | appearance none
52 |
53 | pkg-border($textColor)
54 |
55 | pkg-focus()
56 | pkg-hover()
57 |
58 | &.success
59 | c $successColor
60 |
61 | &.error
62 | c $errorColor
63 |
64 | &::selection
65 | background-color $codeBgColor
66 | color lighten($accentColor, 10)
67 |
68 | .gitpkg-button
69 | c($color)
70 | pkg-outline()
71 | outline-color $color
72 |
73 | background-color: bg = rgb(240, 240, 240)
74 | text-align center
75 | cursor pointer
76 |
77 | pkg-border($textColor)
78 |
79 | pkg-focus()
80 |
81 | pkg-hover()
82 |
83 | *
84 | transition $trans, color 0s
85 |
86 | &:active
87 | pkg-outline()
88 | c darken($accentColor, 25%)
89 | background-color darken(bg, 10%)
90 |
91 | &.icon
92 | width $unitSize
93 | height $unitSize
94 | display flex
95 | justify-content center
96 | align-items center
97 |
98 | &.success
99 | color $successColor
100 | c $successColor
101 |
102 | &.error
103 | color $errorColor
104 | c $errorColor
105 |
106 | @import select
107 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/palette.styl:
--------------------------------------------------------------------------------
1 | // https://vuepress.vuejs.org/config/#palette-styl
2 |
3 | $unitSize = 2.4em
4 |
5 | // colors
6 | $primaryColor = #2196F3;
7 | $successColor = #42b983;
8 | $infoColor = #42A5F5;
9 | $errorColor = #C30000;
10 | $warningColor = darken(#FFE564, 35%);
11 |
12 | $accentColor = #F06292;
13 | // $textColor = #2c3e50;
14 | // $borderColor = #eaecef;
15 | // $codeBgColor = #282c34;
16 | // $arrowBgColor = #ccc;
17 | $badgeTipColor = $infoColor;
18 | $badgeWarningColor = $warningColor;
19 | $badgeErrorColor = $errorColor;
20 | // // layout
21 | // $navbarHeight = 3.6rem;
22 | // $sidebarWidth = 20rem;
23 | // $contentWidth = 740px;
24 | // $homePageWidth = 960px;
25 | // // responsive breakpoints
26 | // $MQNarrow = 959px;
27 | // $MQMobile = 719px;
28 | // $MQMobileNarrow = 419px;
29 |
--------------------------------------------------------------------------------
/docs/.vuepress/styles/select.styl:
--------------------------------------------------------------------------------
1 | .gitpkg-select.v-select
2 | padding 0
3 | position relative
4 | height $unitSize
5 |
6 | box-sizing border-box
7 | & *
8 | box-sizing border-box
9 |
10 | .vs__dropdown-toggle
11 | width 100%
12 | max-width 100%
13 | height 100%
14 |
15 | display flex
16 | justify-content space-between
17 |
18 | pkg-border($textColor)
19 |
20 | pkg-focus()
21 | pkg-hover()
22 |
23 | .vs__actions
24 | display flex
25 |
26 | > *
27 | @extend .gitpkg-button
28 | display flex
29 | justify-content center
30 | align-items center
31 |
32 | height 100%
33 | border none
34 |
35 | .vs__selected-options
36 | display flex
37 |
38 | padding 0 1em
39 |
40 | .vs__selected
41 | flex 0 1 auto
42 | display flex
43 | justify-content center
44 | align-items center
45 |
46 | .vs__search
47 | flex 0 1 auto
48 | display block
49 | width 0
50 | appearance none
51 | border none
52 | outline none
53 |
54 | &.vs--open .vs__open-indicator > *
55 | transform rotate(180deg) scale(1)
56 |
57 | .vs__dropdown-menu
58 | display block
59 | position absolute
60 | top 100%
61 | left 0
62 | z-index 1000
63 | margin 0
64 | padding 0
65 | width 100%
66 | overflow-y auto
67 | border-top-style none
68 | text-align left
69 | list-style none
70 | background white
71 |
72 | .vs__dropdown-option
73 | @extend .gitpkg-button
74 | border-width 0
75 | overflow visible
76 | &.vs__dropdown-option--selected
77 | background-color $accentColor
78 | color white
79 | &.vs__dropdown-option--highlight
80 | @extend .gitpkg-button:hover
81 |
82 | &.relaxed .vs__dropdown-toggle
83 | border 0
84 |
85 | &:not(:hover):not(:focus)
86 | box-shadow 0px 0 1px 0 black
87 |
--------------------------------------------------------------------------------
/docs/.vuepress/yarn-svg-content.js:
--------------------------------------------------------------------------------
1 | export default ` `;
2 |
--------------------------------------------------------------------------------
/docs/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | home: true
3 | heroImage: /cover.svg
4 | footer: MIT Licensed | Copyright © 2020-present Equal Ma
5 | ---
6 |
7 |
8 |
9 | # How to use
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | You can also check out the detailed [guide](./guide/)
18 |
19 |
20 |
--------------------------------------------------------------------------------
/docs/guide/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | sidebar: auto
3 | ---
4 |
5 | # GitPkg Guide
6 |
7 | ## Simplest API
8 |
9 | - Use a sub folder of a repo as dependency (master branch will be used)
10 |
11 | ```
12 | https://gitpkg.now.sh//
13 | ```
14 |
15 | - If you want to use another branch or commit instead
16 |
17 | ```
18 | https://gitpkg.now.sh//?
19 | ```
20 |
21 | ::: tip
22 |
23 | Usually, a commit-ish can be a `branch`, a `commit`, or a `tag`, etc.
24 |
25 | For more information, see: [git docs > commit-ish](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefcommit-ishacommit-ishalsocommittish)
26 |
27 | :::
28 |
29 | - In fact, usage without sub folder is also available:
30 |
31 | `https://gitpkg.now.sh/`
32 |
33 | `https://gitpkg.now.sh/?`
34 |
35 | But `yarn add` and `npm install` support using github url directly:
36 |
37 | ``
38 |
39 | `#`
40 |
41 | Examples:
42 |
43 | ```shell
44 | yarn init -y
45 |
46 | # dep: repo=[EqualMa/gitpkg-hello] > sub folder=[packages/hello]
47 | yarn add https://gitpkg.now.sh/EqualMa/gitpkg-hello/packages/hello
48 |
49 | # dep: [EqualMa/gitpkg-hello] > [packages/core] # branch=[feat/md]
50 | yarn add https://gitpkg.now.sh/EqualMa/gitpkg-hello/packages/core?feat/md
51 | ```
52 |
53 | ## More Formal API
54 |
55 | ```
56 | https://gitpkg.now.sh/api/pkg?url=/
57 | https://gitpkg.now.sh/api/pkg?url=/&commit=
58 | ```
59 |
60 | Or if you want to make the file format clear:
61 |
62 | ```
63 | https://gitpkg.now.sh/api/pkg.tgz?url=&commit=
64 | ```
65 |
66 | ## Custom Scripts
67 |
68 | ### Why we need custom scripts
69 |
70 | Many github repositories contains source code only, which you can't use directly as a npm/yarn dependency.
71 |
72 | So this service provide the option to configure custom scripts
73 |
74 | ### How to use
75 |
76 | #### edit with the Web UI
77 |
78 | All you need is go to the [main page](/),
79 | click the `Add a custom script` button,
80 | edit the script name and content,
81 | then the install command will include the custom scripts.
82 |
83 | You can try the [example](#custom-script-example).
84 |
85 | #### (Advanced) setup the url by yourself
86 |
87 | If you don't want to use the UI, you can setup the url by your self
88 |
89 | - Simplest API
90 |
91 | ```
92 | https://gitpkg.now.sh//?&scripts.=
93 | ```
94 |
95 | - More Formal API
96 |
97 | ```
98 | https://gitpkg.now.sh/pkg?url=/&scripts.=
99 | https://gitpkg.now.sh/pkg?url=/&commit=&scripts.=
100 | ```
101 |
102 | ::: warning
103 | `` and `` must NOT contain special chars including `& ? =`. You can use `encodeURIComponent` to encode them before putting them in the query param.
104 | :::
105 |
106 | ::: warning
107 | If you use windows, then using `yarn install ` or `npm install ` if `` contains `&` may lead to errors.
108 |
109 | In such cases, you have to manually add `"": ""` to the `dependency` or `devDependency` of `package.json`.
110 | :::
111 |
112 | ##### replace, append to, or prepend to the original script
113 |
114 | If the original `package.json` contains the script with the same name,
115 | you can choose to whether to replace it. For example:
116 |
117 | The `package.json` is like:
118 |
119 | ```json
120 | {
121 | // ...
122 | "scripts": {
123 | "postinstall": "node original-install.js"
124 | }
125 | // ...
126 | }
127 | ```
128 |
129 | - To **replace** the original, just use `scripts.postinstall=command-from-gitpkg`,
130 | then the generated `package.json` will be like:
131 |
132 | ```json
133 | {
134 | "scripts": {
135 | "postinstall": "command-from-gitpkg"
136 | }
137 | }
138 | ```
139 |
140 | - To **append** to the original, add `&&` (encoded as `%26%26`) **before** your script content: `scripts.postinstall=%26%26command-from-gitpkg`.
141 | Then the generated `package.json` will be like:
142 |
143 | ```json
144 | {
145 | "scripts": {
146 | "postinstall": "node original-install.js && command-from-gitpkg"
147 | }
148 | }
149 | ```
150 |
151 | - To **prepend** to the original, add `&&` **after** your script content: `scripts.postinstall=command-from-gitpkg%26%26`.
152 | Then the generated `package.json` will be like:
153 |
154 | ```json
155 | {
156 | "scripts": {
157 | "postinstall": "command-from-gitpkg && node original-install.js"
158 | }
159 | }
160 | ```
161 |
162 | ### (Advanced) How this function is implemented
163 |
164 | GitPkg service process the tar file of the github repo in the form of stream,
165 | so that only a few memory is used.
166 |
167 | This means when a user (yarn or npm actually) requests `my-sub-dir` folder from repo `example-user/example-repo`,
168 | GitPkg service requests the tarball of whole repo `example-user/example-repo` from github,
169 | open a stream from the tarball response.
170 |
171 | Then the stream is parsed as an `tar entry stream`,
172 | and each entry is checked for whether it is in `my-sub-dir` folder.
173 |
174 | If yes, this entry is added to the stream which responses to the user.
175 | If not, this entry is ignored.
176 |
177 | To add the custom scripts, when an entry's path is `my-sub-dir/package.json`,
178 | this entry's file content will be loaded,
179 | and modified (the custom scripts are added to the `scripts` prop).
180 | Then the modified version is added to the stream which responses to the user.
181 |
182 | So when the user, or yarn and npm, receive the tarball,
183 | the tarball only contains files under `my-sub-dir`.
184 | And if custom scripts are specified,
185 | the `package.json` is modified to include the specified scripts.
186 |
187 | This is how GitPkg works.
188 |
189 | ## Examples
190 |
191 | ### Custom script example
192 |
193 | This example shows how to install [EqualMa/gitpkg-hello > packages/hello-ts](https://github.com/EqualMa/gitpkg-hello/tree/master/packages/hello-ts) as dependency.
194 | The sub folder of this repo only contains typescript source code so we need to use custom scripts:
195 | `scripts.postinstall=npm install --ignore-scripts && npm run build`
196 |
197 | ```shell
198 | mkdir hello-gitpkg
199 | cd hello-gitpkg
200 | npm init -y
201 | npm install 'https://gitpkg.now.sh/EqualMa/gitpkg-hello/packages/hello-ts?master&scripts.postinstall=npm%20install%20--ignore-scripts%20%26%26%20npm%20run%20build'
202 | ```
203 |
204 | Then make a new file `test.js`
205 |
206 | ```js
207 | const { hello } = require("hello-ts");
208 | console.log(hello("world"));
209 | ```
210 |
211 | Running it should outputs `Hello world from TypeScript!`
212 |
--------------------------------------------------------------------------------
/lerna.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "independent",
3 | "npmClient": "yarn",
4 | "useWorkspaces": true
5 | }
6 |
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "build": {},
3 | "rewrites": [
4 | {
5 | "source": "/([^/]+)(/)?",
6 | "destination": "/$1/index.html"
7 | },
8 | {
9 | "source": "/api/pkg",
10 | "destination": "/api/pkg.ts"
11 | },
12 | {
13 | "source": "/api/pkg.tgz",
14 | "destination": "/api/pkg.ts"
15 | },
16 | {
17 | "source": "/((?:[^?]+/)+[^?]+)(\\?.+)?",
18 | "destination": "/api/pkg-from-url"
19 | }
20 | ]
21 | }
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gitpkg/site",
3 | "version": "0.1.0",
4 | "license": "MIT",
5 | "private": true,
6 | "workspaces": [
7 | "docs/.vuepress",
8 | "tools/*",
9 | "packages/*"
10 | ],
11 | "repository": {
12 | "type": "git",
13 | "url": "https://github.com/EqualMa/gitpkg.git"
14 | },
15 | "scripts": {
16 | "lint": "eslint --cache --ext .js,.ts",
17 | "lint:fix": "eslint --cache --max-warnings 0 --fix",
18 | "test": "lerna run test",
19 | "now-build": "yarn run build && yarn run test",
20 | "build": "lerna run build"
21 | },
22 | "devDependencies": {
23 | "@babel/core": "^7.8.7",
24 | "@babel/preset-env": "^7.8.7",
25 | "@babel/register": "^7.8.6",
26 | "@gitpkg/common": "*",
27 | "@now/node": "^1.4.1",
28 | "@types/jest": "^25.1.3",
29 | "@typescript-eslint/eslint-plugin": "^2.19.2",
30 | "@typescript-eslint/parser": "^2.19.2",
31 | "eslint": "^6.8.0",
32 | "eslint-config-prettier": "^6.10.0",
33 | "eslint-plugin-node": "^11.0.0",
34 | "eslint-plugin-prettier": "^3.1.2",
35 | "husky": "^4.2.3",
36 | "imagemin-lint-staged": "^0.4.0",
37 | "jest": "^25.1.0",
38 | "lerna": "^3.20.2",
39 | "lint-staged": "^10.0.7",
40 | "now": "^17.0.4",
41 | "prettier": "1.19.1",
42 | "rimraf": "^3.0.2",
43 | "ts-jest": "^25.2.1",
44 | "ts-node": "^8.6.2",
45 | "typescript": "^3.7.5"
46 | },
47 | "dependencies": {
48 | "@gitpkg/core": "^1.0.0-alpha"
49 | },
50 | "husky": {
51 | "hooks": {
52 | "pre-commit": "lint-staged && lerna run precommit"
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/packages/core/.eslintignore:
--------------------------------------------------------------------------------
1 | /dist
2 |
--------------------------------------------------------------------------------
/packages/core/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require("@gitpkg/common/eslint")(__dirname);
2 |
--------------------------------------------------------------------------------
/packages/core/.lintstagedrc.yml:
--------------------------------------------------------------------------------
1 | "!(.eslintrc).{js,ts}": "yarn run lint:fix"
2 |
--------------------------------------------------------------------------------
/packages/core/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require("@gitpkg/common/babel")();
2 |
--------------------------------------------------------------------------------
/packages/core/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require("@gitpkg/common/jest")();
2 |
--------------------------------------------------------------------------------
/packages/core/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gitpkg/core",
3 | "version": "1.0.0-alpha",
4 | "main": "dist/index.js",
5 | "types": "dist/index.d.js",
6 | "license": "MIT",
7 | "scripts": {
8 | "test": "jest",
9 | "precommit": "lint-staged",
10 | "lint:fix": "eslint --cache --max-warnings 0 --fix",
11 | "build": "rimraf dist && tsc -p tsconfig.prod.json --outDir dist --module CommonJS --declaration --sourceMap --declarationMap"
12 | },
13 | "dependencies": {
14 | "got": "^10.5.5",
15 | "path-to-regexp": "^6.1.0",
16 | "tar-transform": "^1.0.0"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/core/src/api/codeload-url.ts:
--------------------------------------------------------------------------------
1 | export function codeloadUrl (repo: string, commit: string) {
2 | if (!repo) throw Error('Missing repo')
3 | if (!commit) throw Error('Missing commit')
4 |
5 | return `https://codeload.github.com/${repo}/tar.gz/${commit}`;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/core/src/api/download-git-pkg.ts:
--------------------------------------------------------------------------------
1 | import got from "got";
2 | import * as tt from "tar-transform";
3 | import { codeloadUrl } from "./codeload-url";
4 | import { extractSubFolder } from "../tar/extract-sub-folder";
5 | import { customScripts } from "../tar/custom-scripts";
6 | import { prependPath } from "../tar/prepend-path";
7 | import { PkgOptions } from "../parse-url-query";
8 |
9 | export type PipelineItem =
10 | | NodeJS.ReadableStream
11 | | NodeJS.WritableStream
12 | | NodeJS.ReadWriteStream;
13 |
14 | export function pipelineToPkgTarEntries(
15 | pkgOpts: PkgOptions,
16 | ): NodeJS.ReadWriteStream[] {
17 | const { customScripts: cs, commitIshInfo: cii } = pkgOpts;
18 |
19 | return [
20 | extractSubFolder(cii.subdir),
21 | cs && cs.length > 0 && customScripts(cs),
22 | prependPath("package/"),
23 | ].filter(Boolean) as NodeJS.ReadWriteStream[];
24 | }
25 |
26 | export function pipelineToDownloadGitPkg(pkgOpts: PkgOptions): PipelineItem[] {
27 | const { commitIshInfo: cii } = pkgOpts;
28 |
29 | const tgzUrl = codeloadUrl(`${cii.user}/${cii.repo}`, cii.commit);
30 |
31 | return [
32 | got.stream(tgzUrl),
33 | tt.extract({ gzip: true }),
34 | ...pipelineToPkgTarEntries(pkgOpts),
35 | tt.pack({ gzip: true }),
36 | ];
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/src/api/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./download-git-pkg";
2 |
--------------------------------------------------------------------------------
/packages/core/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./api";
2 | export { getDefaultParser } from "./parse-url-query";
3 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/default-parser.ts:
--------------------------------------------------------------------------------
1 | import { PkgOptionsParser } from "./parser";
2 | import { getUrlAndCommitPlugin, customScriptsPlugin } from "./plugins";
3 |
4 | const getParser = (parseFromUrl: boolean) =>
5 | new PkgOptionsParser()
6 | .withPlugin(getUrlAndCommitPlugin(parseFromUrl))
7 | .withPlugin(customScriptsPlugin);
8 |
9 | const parserFromUrl = getParser(true);
10 | const parserFromQuery = getParser(false);
11 |
12 | export const getDefaultParser = (parseFromUrl = false) =>
13 | parseFromUrl ? parserFromUrl : parserFromQuery;
14 |
15 | export type PkgOptions = ReturnType<
16 | typeof getDefaultParser
17 | > extends PkgOptionsParser
18 | ? T
19 | : never;
20 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/error.ts:
--------------------------------------------------------------------------------
1 | export class UrlInvalidError extends Error {
2 | constructor(msg?: string) {
3 | super(msg);
4 | }
5 | }
6 |
7 | export class QueryParamsInvalidError extends Error {
8 | constructor(public readonly key: string | string[], msg?: string) {
9 | super(msg);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/get-value.ts:
--------------------------------------------------------------------------------
1 | import { PkgUrlAndCommitOptions } from "./plugins";
2 |
3 | export function getValueOfQuery(
4 | query: import("@now/node").NowRequestQuery,
5 | key: string,
6 | options: PkgUrlAndCommitOptions,
7 | ): undefined | string | string[] {
8 | const commitFromUrl: string | undefined = options.parsedFromUrl
9 | ? options.commit
10 | : undefined;
11 | const v: undefined | string | string[] = query[key];
12 |
13 | if (key === commitFromUrl) {
14 | if (typeof v === "string") {
15 | return v === commitFromUrl ? undefined : v;
16 | } else if (Array.isArray(v)) {
17 | if (v.length === 0) return undefined;
18 | if (v[0] === commitFromUrl) {
19 | if (v.length === 1) return undefined;
20 | else v.slice(1);
21 | }
22 | }
23 | } else return v;
24 | }
25 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./parser";
2 | export * from "./default-parser";
3 |
4 | export * from "./error";
5 | export * from "./get-value";
6 | export * from "./plugins";
7 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/parser.ts:
--------------------------------------------------------------------------------
1 | type Overwrite = {
2 | [K in keyof T | keyof U]: K extends keyof U
3 | ? U[K]
4 | : K extends keyof T
5 | ? T[K]
6 | : never;
7 | };
8 |
9 | export type PkgOptionsParserPlugin = (
10 | url: string,
11 | query: import("@now/node").NowRequestQuery,
12 | previousOptions: T,
13 | ) => U;
14 |
15 | export class PkgOptionsParser {
16 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
17 | private plugins: PkgOptionsParserPlugin[] = [];
18 |
19 | public withPlugin(
20 | plugin: PkgOptionsParserPlugin,
21 | ): PkgOptionsParser> {
22 | const p = new PkgOptionsParser>();
23 | p.plugins.push(...this.plugins, plugin);
24 | return p;
25 | }
26 |
27 | public parse(
28 | requestUrl: string,
29 | query: import("@now/node").NowRequestQuery,
30 | ): T {
31 | return this.plugins.reduce((options, plugin) => {
32 | return {
33 | ...(options as object),
34 | ...plugin(requestUrl, query, options),
35 | };
36 | }, {}) as T;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/custom-scripts/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./plugin";
2 | export * from "./parse-query";
3 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/custom-scripts/parse-query.spec.ts:
--------------------------------------------------------------------------------
1 | import * as impl from "./parse-query";
2 |
3 | test("check if query key is custom script", () => {
4 | expect(impl.queryKeyIsCustomScript("scripts.postinstall")).toBe(true);
5 | expect(impl.queryKeyIsCustomScript("scripts.")).toBe(true);
6 |
7 | expect(impl.queryKeyIsCustomScript("scripts")).toBe(false);
8 | });
9 |
10 | test("parse script (string)", () => {
11 | expect(() => {
12 | impl.parseQueryAsCustomScript("foo", "yarn build");
13 | }).toThrowError();
14 |
15 | expect(
16 | impl.parseQueryAsCustomScript("scripts.postinstall", "yarn build"),
17 | ).toStrictEqual({
18 | name: "postinstall",
19 | script: "yarn build",
20 | type: "replace",
21 | });
22 |
23 | expect(
24 | impl.parseQueryAsCustomScript("scripts.postinstall", "&& yarn build"),
25 | ).toStrictEqual({
26 | name: "postinstall",
27 | script: "yarn build",
28 | type: "append",
29 | });
30 |
31 | expect(
32 | impl.parseQueryAsCustomScript("scripts.postinstall", "yarn build &&"),
33 | ).toStrictEqual({
34 | name: "postinstall",
35 | script: "yarn build",
36 | type: "prepend",
37 | });
38 | });
39 |
40 | test("parse script (string array)", () => {
41 | expect(
42 | impl.parseQueryAsCustomScript("scripts.postinstall", [
43 | "yarn install",
44 | "yarn build",
45 | ]),
46 | ).toStrictEqual({
47 | name: "postinstall",
48 | script: "yarn install && yarn build",
49 | type: "replace",
50 | });
51 |
52 | expect(
53 | impl.parseQueryAsCustomScript("scripts.postinstall", [
54 | "&& yarn install",
55 | "yarn build",
56 | ]),
57 | ).toStrictEqual({
58 | name: "postinstall",
59 | script: "yarn install && yarn build",
60 | type: "append",
61 | });
62 |
63 | expect(
64 | impl.parseQueryAsCustomScript("scripts.postinstall", [
65 | "yarn install",
66 | "yarn build &&",
67 | ]),
68 | ).toStrictEqual({
69 | name: "postinstall",
70 | script: "yarn install && yarn build",
71 | type: "prepend",
72 | });
73 | });
74 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/custom-scripts/parse-query.ts:
--------------------------------------------------------------------------------
1 | export interface PkgCustomScript {
2 | name: string;
3 | script: string;
4 | type: "prepend" | "append" | "replace";
5 | }
6 |
7 | const SCRIPTS_PREFIX = "scripts.";
8 |
9 | export function queryKeyIsCustomScript(
10 | key: string | symbol | number,
11 | ): key is string {
12 | return typeof key === "string" && key.startsWith(SCRIPTS_PREFIX);
13 | }
14 |
15 | function trimAndAnd(v: string) {
16 | return v
17 | .slice(v.startsWith("&&") ? 2 : 0, v.endsWith("&&") ? -2 : undefined)
18 | .trim();
19 | }
20 |
21 | export function parseQueryAsCustomScript(
22 | key: string,
23 | value: string | string[],
24 | ): PkgCustomScript {
25 | if (!queryKeyIsCustomScript(key)) {
26 | throw new Error("query key is not valid as a custom script");
27 | }
28 |
29 | let type: PkgCustomScript["type"] | undefined = undefined;
30 | let script: string;
31 |
32 | const name = key.slice(SCRIPTS_PREFIX.length);
33 |
34 | if (typeof value === "string") {
35 | const v = value.trim();
36 | type = v.startsWith("&&")
37 | ? "append"
38 | : v.endsWith("&&")
39 | ? "prepend"
40 | : "replace";
41 | script = trimAndAnd(v);
42 | } else {
43 | const s = [];
44 | for (const [i, val] of value.entries()) {
45 | const v = val.trim();
46 | if (i === 0 && v.startsWith("&&")) {
47 | type = "append";
48 | } else if (i === value.length - 1 && v.endsWith("&&")) {
49 | type = "prepend";
50 | }
51 |
52 | s.push(trimAndAnd(v));
53 | }
54 | script = s.join(" && ");
55 | }
56 |
57 | return {
58 | name,
59 | script,
60 | type: type === undefined ? "replace" : type,
61 | };
62 | }
63 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/custom-scripts/plugin.ts:
--------------------------------------------------------------------------------
1 | import { PkgOptionsParserPlugin } from "../../parser";
2 | import {
3 | PkgCustomScript,
4 | queryKeyIsCustomScript,
5 | parseQueryAsCustomScript,
6 | } from "./parse-query";
7 | import { PkgUrlAndCommitOptions } from "../url-and-commit";
8 | import { getValueOfQuery } from "../../get-value";
9 |
10 | export interface PkgCustomScriptsOptions {
11 | customScripts: PkgCustomScript[];
12 | }
13 |
14 | export const customScriptsPlugin: PkgOptionsParserPlugin<
15 | PkgUrlAndCommitOptions,
16 | PkgCustomScriptsOptions
17 | > = (requestUrl, query, previousOptions) => {
18 | return {
19 | customScripts: Reflect.ownKeys(query)
20 | .map(k => {
21 | if (queryKeyIsCustomScript(k)) {
22 | const v = getValueOfQuery(query, k, previousOptions);
23 | if (v) {
24 | return parseQueryAsCustomScript(k, v);
25 | } else return null;
26 | } else {
27 | return null;
28 | }
29 | })
30 | .filter((Boolean as unknown) as (v: unknown) => v is PkgCustomScript),
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./url-and-commit";
2 | export * from "./custom-scripts";
3 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/url-and-commit/commit-ish.ts:
--------------------------------------------------------------------------------
1 | import { match } from "path-to-regexp";
2 | import { PkgUrlAndCommitOptions } from "./plugin";
3 | import { UrlInvalidError, QueryParamsInvalidError } from "../../error";
4 |
5 | interface CommitIshInfoMatchResult {
6 | user: string;
7 | repo: string;
8 | subdirs?: string[];
9 | }
10 | const matchCommitIshInfo = match(
11 | ":user/:repo/:subdirs*(/)?",
12 | );
13 |
14 | export interface CommitIshInfo {
15 | user: string;
16 | repo: string;
17 | /** "" or a string which ends with "/" */
18 | subdir: string;
19 | subdirs: string[] | undefined;
20 | commit: string;
21 | }
22 |
23 | const DEFAULT_COMMIT_ISH = "master";
24 |
25 | export function parseCommitIshInfo(
26 | url: PkgUrlAndCommitOptions["url"],
27 | commit: PkgUrlAndCommitOptions["commit"],
28 | parsedFromUrl: PkgUrlAndCommitOptions["parsedFromUrl"],
29 | ): CommitIshInfo {
30 | const res = matchCommitIshInfo(url);
31 |
32 | if (res === false) {
33 | throw parsedFromUrl
34 | ? new UrlInvalidError()
35 | : new QueryParamsInvalidError("url");
36 | }
37 |
38 | const { user, repo, subdirs } = res.params;
39 | return {
40 | user,
41 | repo,
42 | subdir: subdirs ? subdirs.join("/") + "/" : "",
43 | subdirs,
44 | commit: commit || DEFAULT_COMMIT_ISH,
45 | };
46 | }
47 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/url-and-commit/from-query.ts:
--------------------------------------------------------------------------------
1 | import { PkgOptionsParserPlugin } from "../../parser";
2 | import { QueryParamsInvalidError } from "../../error";
3 | import { PkgUrlAndCommitOptions } from "./plugin";
4 | import { parseCommitIshInfo } from "./commit-ish";
5 |
6 | export const fromQuery: PkgOptionsParserPlugin<
7 | unknown,
8 | PkgUrlAndCommitOptions
9 | > = (requestUrl, query) => {
10 | const { url, commit } = query;
11 | if (typeof url !== "string") {
12 | throw new QueryParamsInvalidError("url");
13 | }
14 | if (typeof commit !== "string" && typeof commit !== "undefined") {
15 | throw new QueryParamsInvalidError("commit");
16 | }
17 | return {
18 | url,
19 | commit,
20 | parsedFromUrl: false,
21 | commitIshInfo: parseCommitIshInfo(url, commit, false),
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/url-and-commit/from-url.ts:
--------------------------------------------------------------------------------
1 | import { match } from "path-to-regexp";
2 | import { PkgOptionsParserPlugin } from "../../parser";
3 | import { PkgUrlAndCommitOptions } from "./plugin";
4 | import { parseCommitIshInfo } from "./commit-ish";
5 | import { QueryParamsInvalidError, UrlInvalidError } from "../../error";
6 |
7 | const matchFromUrl = match(
8 | "/:url((?:[^?]+/)+[^?]+){\\?:commit([^?&=]+)}?(.+)?",
9 | );
10 |
11 | interface MatchResult {
12 | url: string;
13 | commit?: string;
14 | }
15 |
16 | export const fromUrl: PkgOptionsParserPlugin<
17 | unknown,
18 | PkgUrlAndCommitOptions
19 | > = (requestUrl, query) => {
20 | const res = matchFromUrl(requestUrl);
21 | if (!res) {
22 | throw new UrlInvalidError();
23 | } else {
24 | const { url: u, commit: c } = res.params;
25 | const url = decodeURIComponent(u);
26 | const commit = c && decodeURIComponent(c);
27 |
28 | if (query.commit)
29 | // url = /foo/bar?master&commit=master
30 | throw new QueryParamsInvalidError(
31 | "commit",
32 | `param commit is specified from both url(${url}) and query(commit=${commit})`,
33 | );
34 |
35 | return {
36 | url,
37 | commit,
38 | parsedFromUrl: true,
39 | commitIshInfo: parseCommitIshInfo(url, commit, true),
40 | };
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/url-and-commit/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./plugin";
2 |
--------------------------------------------------------------------------------
/packages/core/src/parse-url-query/plugins/url-and-commit/plugin.ts:
--------------------------------------------------------------------------------
1 | import { PkgOptionsParserPlugin } from "../../parser";
2 | import { fromUrl } from "./from-url";
3 | import { fromQuery } from "./from-query";
4 | import { CommitIshInfo } from "./commit-ish";
5 |
6 | export { CommitIshInfo } from "./commit-ish";
7 | export interface PkgUrlAndCommitOptions {
8 | url: string;
9 | commit: undefined | string;
10 | parsedFromUrl: boolean;
11 | commitIshInfo: CommitIshInfo;
12 | }
13 |
14 | export const getUrlAndCommitPlugin = (
15 | parseFromUrl = false,
16 | ): PkgOptionsParserPlugin =>
17 | parseFromUrl ? fromUrl : fromQuery;
18 |
--------------------------------------------------------------------------------
/packages/core/src/tar/custom-scripts.spec.ts:
--------------------------------------------------------------------------------
1 | import * as impl from "./custom-scripts";
2 | import { PkgCustomScript } from "../parse-url-query";
3 | import { TarEntry } from "tar-transform";
4 | import { tarEntries, getEntries } from "../../test/util/tar-entry";
5 | import { Readable, pipeline as _pl } from "stream";
6 | import { promisify } from "util";
7 |
8 | const pipeline = promisify(_pl);
9 |
10 | const ADD_TYPES: PkgCustomScript["type"][] = ["append", "prepend", "replace"];
11 |
12 | type TestCase = [
13 | Record,
14 | PkgCustomScript[],
15 | Record,
16 | ];
17 |
18 | const testCases = (): TestCase[] => [
19 | [{}, [], {}],
20 | ...ADD_TYPES.map(type => [
21 | {},
22 | [{ name: "build", script: "tsc", type }],
23 | { build: "tsc" },
24 | ]),
25 | ...ADD_TYPES.map(type => {
26 | const res = {
27 | append: "tsc && echo 'success'",
28 | prepend: "echo 'success' && tsc",
29 | replace: "echo 'success'",
30 | };
31 | return [
32 | { build: "tsc" },
33 | [{ name: "build", script: "echo 'success'", type }],
34 | { build: res[type] },
35 | ];
36 | }),
37 | ...ADD_TYPES.map(type => {
38 | const res = {
39 | append: "tsc && echo 'success'",
40 | prepend: "echo 'success' && tsc",
41 | replace: "echo 'success'",
42 | };
43 | return [
44 | { build: "tsc", test: "jest" },
45 | [
46 | { name: "build", script: "echo 'success'", type },
47 | { name: "postinstall", script: "npm run build", type: "replace" },
48 | ],
49 | {
50 | build: res[type],
51 | test: "jest",
52 | postinstall: "npm run build",
53 | },
54 | ];
55 | }),
56 | ];
57 |
58 | test("add scripts to package.json", () => {
59 | for (const [scripts, add, res] of testCases()) {
60 | const pkg = { scripts };
61 | impl.addScriptsToPkgJson(pkg, add);
62 | expect(pkg).toEqual({ scripts: res });
63 | }
64 | });
65 |
66 | function* tarEntriesWithPkgJson(
67 | insertIndex = 0,
68 | content: string,
69 | ...args: Parameters
70 | ): Generator {
71 | let inserted = false;
72 |
73 | const pkgJson: TarEntry = {
74 | headers: { name: "package.json" },
75 | content,
76 | };
77 |
78 | let i = 0;
79 | for (const e of tarEntries(...args)) {
80 | if (i === insertIndex) {
81 | inserted = true;
82 | yield pkgJson;
83 | }
84 | yield e;
85 | i++;
86 | }
87 |
88 | if (!inserted) yield pkgJson;
89 | }
90 |
91 | test("add scripts to tar entry stream", () =>
92 | Promise.all(
93 | testCases()
94 | .map(([scripts, add, res]) => {
95 | return [0, 5, -1].map(insertIndex => {
96 | const r = Readable.from(
97 | tarEntriesWithPkgJson(insertIndex, JSON.stringify({ scripts }), {
98 | count: 10,
99 | }),
100 | );
101 | const t = impl.customScripts(add);
102 |
103 | return [
104 | expect(pipeline(r, t)).resolves.toBeUndefined(),
105 | expect(getEntries(t)).resolves.toEqual([
106 | ...tarEntriesWithPkgJson(
107 | insertIndex,
108 | JSON.stringify({ scripts: res }, undefined, 2),
109 | { count: 10 },
110 | ),
111 | ]),
112 | ];
113 | });
114 | })
115 | .flat(2),
116 | ));
117 |
--------------------------------------------------------------------------------
/packages/core/src/tar/custom-scripts.ts:
--------------------------------------------------------------------------------
1 | import { modifySingleFile } from "./modify-single-file";
2 | import { PkgCustomScript } from "../parse-url-query";
3 |
4 | export function addScriptsToPkgJson(
5 | pkgJson: Record,
6 | scripts: PkgCustomScript[],
7 | ) {
8 | const pkgScripts = (pkgJson.scripts || (pkgJson.scripts = {})) as Record<
9 | string,
10 | string
11 | >;
12 |
13 | for (const s of scripts) {
14 | const { type, name, script } = s;
15 |
16 | if (type === "replace") {
17 | pkgScripts[name] = script;
18 | } else if (type === "prepend") {
19 | const original = pkgScripts[name];
20 | const str = original ? " && " + original : "";
21 | pkgScripts[name] = script + str;
22 | } else if (type === "append") {
23 | const original = pkgScripts[name];
24 | const str = original ? original + " && " : "";
25 | pkgScripts[name] = str + script;
26 | } else throw new Error("prop type is invalid: " + type);
27 | }
28 | }
29 |
30 | export const customScripts = (scripts: PkgCustomScript[]) =>
31 | modifySingleFile("package.json", async function(entry) {
32 | const pkgJson = JSON.parse(await this.util.stringContentOfTarEntry(entry));
33 | addScriptsToPkgJson(pkgJson, scripts);
34 | return { content: JSON.stringify(pkgJson, undefined, 2) };
35 | });
36 |
--------------------------------------------------------------------------------
/packages/core/src/tar/extract-sub-folder.spec.ts:
--------------------------------------------------------------------------------
1 | import { extractSubFolder } from "./extract-sub-folder";
2 | import { TarEntry } from "tar-transform";
3 | import { Readable, pipeline as _pipeline } from "stream";
4 | import { tarEntries, getEntries } from "../../test/util/tar-entry";
5 |
6 | import { promisify } from "util";
7 | const pipeline = promisify(_pipeline);
8 |
9 | test("do not extract sub folder (only extract root folder)", () => {
10 | const read = Readable.from(tarEntries({ root: "root/" }));
11 | const t = extractSubFolder("");
12 | return Promise.all([
13 | pipeline(read, t),
14 | expect(getEntries(t)).resolves.toEqual([
15 | ...tarEntries({ root: "" }),
16 | ]),
17 | ]);
18 | });
19 |
20 | test("extract sub folder", () => {
21 | const sub = "dir1/";
22 |
23 | const read = Readable.from(tarEntries({ root: "root/" }));
24 | const t = extractSubFolder(sub);
25 | return Promise.all([
26 | pipeline(read, t),
27 | expect(getEntries(t)).resolves.toEqual(
28 | [...tarEntries({ root: "" })].filter(e => e.headers.name.startsWith(sub)),
29 | ),
30 | ]);
31 | });
32 |
33 | test("throw error when there is multiple files or dirs at root", async () => {
34 | const read = Readable.from(tarEntries({ root: "" }));
35 | const t = extractSubFolder("dir1");
36 |
37 | const done = expect(pipeline(read, t)).rejects.toThrowError();
38 | t.read();
39 |
40 | await done;
41 | });
42 |
--------------------------------------------------------------------------------
/packages/core/src/tar/extract-sub-folder.ts:
--------------------------------------------------------------------------------
1 | import * as tar from "tar-transform";
2 |
3 | /**
4 | *
5 | * @param subFolder should be "" or end with "/"
6 | * @param prepend should be "" or end with "/"
7 | */
8 | export const extractSubFolder = (subFolder: string, prepend = "") =>
9 | tar.transform<{ root: undefined | string }>({
10 | onEntry(entry): true {
11 | const ctx = this.ctx;
12 | const {
13 | headers,
14 | headers: { name },
15 | } = entry;
16 | if (ctx.root === undefined) {
17 | if (entry.headers.type !== "directory") {
18 | throw new Error("invalid source file: first entry is not directory");
19 | }
20 | ctx.root = name;
21 | return this.pass(entry);
22 | } else if (name.startsWith(ctx.root)) {
23 | if (headers.pax && headers.pax.path !== name) {
24 | throw new Error(
25 | "source file is not valid due to tarball pax header mismatch",
26 | );
27 | }
28 |
29 | const dir = ctx.root + subFolder;
30 | if (name.startsWith(dir) && name.length > dir.length) {
31 | const newHeaders = this.util.headersWithNewName(
32 | headers,
33 | prepend + name.slice(dir.length),
34 | );
35 |
36 | return this.push({ ...entry, headers: newHeaders });
37 | } else return this.pass(entry);
38 | } else {
39 | throw new Error("invalid source file: multiple dirs in root");
40 | }
41 | },
42 | initCtx: { root: undefined },
43 | });
44 |
--------------------------------------------------------------------------------
/packages/core/src/tar/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./custom-scripts";
2 | export * from "./extract-sub-folder";
3 | export * from "./modify-single-file";
4 | export * from "./prepend-path";
5 |
--------------------------------------------------------------------------------
/packages/core/src/tar/modify-single-file.ts:
--------------------------------------------------------------------------------
1 | import { transform, TarEntry, TarEntryTransformer } from "tar-transform";
2 |
3 | type MaybePromise = T | Promise;
4 |
5 | export const modifySingleFile = (
6 | filePath: string,
7 | modify: (
8 | this: TarEntryTransformer<{ matched: boolean }>,
9 | entry: TarEntry,
10 | ) => MaybePromise<
11 | { content: string } | { stream: import("stream").Readable }
12 | >,
13 | ) =>
14 | transform({
15 | async onEntry(entry): Promise {
16 | if (entry.headers.name === filePath) {
17 | if (this.ctx.matched) {
18 | throw new Error(`invalid state`);
19 | }
20 | this.ctx.matched = true;
21 | const data = await modify.call(this, entry);
22 | return this.push({
23 | headers: entry.headers,
24 | ...data,
25 | });
26 | } else {
27 | return this.push(entry);
28 | }
29 | },
30 | onEnd() {
31 | if (!this.ctx.matched) {
32 | throw new Error(`file not found: ${filePath}`);
33 | }
34 | },
35 | initCtx: {
36 | matched: false,
37 | },
38 | });
39 |
--------------------------------------------------------------------------------
/packages/core/src/tar/prepend-path.spec.ts:
--------------------------------------------------------------------------------
1 | import * as impl from "./prepend-path";
2 | import { tarEntries, getEntries } from "../../test/util/tar-entry";
3 | import { Readable, pipeline as _pl } from "stream";
4 | import { promisify } from "util";
5 |
6 | const pipeline = promisify(_pl);
7 |
8 | test("prepend path", () =>
9 | Promise.all(
10 | [undefined, "", "root", "root/"]
11 | .map(prepend =>
12 | ["", "d2/"].map(root => {
13 | const r = Readable.from(tarEntries({ root }));
14 | const t = impl.prependPath(prepend);
15 |
16 | return [
17 | expect(pipeline(r, t)).resolves.toBeUndefined(),
18 | expect(getEntries(t)).resolves.toEqual(
19 | [...tarEntries({ root })].map(e => ({
20 | ...e,
21 | headers: {
22 | ...e.headers,
23 | name: (prepend || "") + e.headers.name,
24 | },
25 | })),
26 | ),
27 | ];
28 | }),
29 | )
30 | .flat(2),
31 | ));
32 |
--------------------------------------------------------------------------------
/packages/core/src/tar/prepend-path.ts:
--------------------------------------------------------------------------------
1 | import { transform } from "tar-transform";
2 |
3 | /**
4 | *
5 | * @param prepend should be "" or end with "/". For example: `"package/"`
6 | */
7 | export const prependPath = (prepend = "") =>
8 | transform({
9 | onEntry(entry) {
10 | this.push({
11 | ...entry,
12 | headers: this.util.headersWithNewName(
13 | entry.headers,
14 | prepend + entry.headers.name,
15 | ),
16 | });
17 | },
18 | });
19 |
--------------------------------------------------------------------------------
/packages/core/test/util/tar-entry.ts:
--------------------------------------------------------------------------------
1 | import { TarEntry, isTarEntry } from "tar-transform";
2 | import { Readable } from "stream";
3 |
4 | export async function getEntries(r: Readable) {
5 | const entries: TarEntry[] = [];
6 | for await (const v of r) {
7 | if (isTarEntry(v)) {
8 | entries.push(v);
9 | } else {
10 | throw new Error("invalid tar entry");
11 | }
12 | }
13 | return entries;
14 | }
15 |
16 | export function* tarEntries({
17 | count = 10,
18 | depth = 3,
19 | root = "",
20 | } = {}): Generator {
21 | const dirs: Record = {};
22 | if (root) {
23 | dirs[root] = true;
24 | yield { headers: { name: root, type: "directory" } };
25 | }
26 | for (let i = 0; i < count; i++) {
27 | const dirName =
28 | root + [...new Array(i % depth).keys()].map(dir => `dir${dir}/`).join("");
29 |
30 | if (dirName && !dirs[dirName]) {
31 | dirs[dirName] = true;
32 | yield { headers: { name: dirName, type: "directory" } };
33 | }
34 | const fileName = dirName + `file${i}.data`;
35 | yield { headers: { name: fileName }, content: String(i) };
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@gitpkg/common/tsconfig",
3 | "include": ["src/**/*.ts", "test/**/*.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/packages/core/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@gitpkg/common/tsconfig",
3 | "include": ["src/**/*.ts"],
4 | "exclude": ["node_modules", "**/*.spec.ts"]
5 | }
6 |
--------------------------------------------------------------------------------
/tools/common/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = require("./eslint/node")();
2 |
--------------------------------------------------------------------------------
/tools/common/.lintstagedrc.yml:
--------------------------------------------------------------------------------
1 | "!(.eslintrc).js": "yarn run lint:fix"
2 |
--------------------------------------------------------------------------------
/tools/common/babel/index.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | presets: [
3 | [
4 | "@babel/env",
5 | {
6 | targets: {
7 | node: "6",
8 | },
9 | // useBuiltIns: "usage", // TODO chore: core-js@3
10 | },
11 | ],
12 | ],
13 | });
14 |
--------------------------------------------------------------------------------
/tools/common/eslint/index.js:
--------------------------------------------------------------------------------
1 | module.exports = rootDir => ({
2 | root: true,
3 | overrides: [
4 | {
5 | files: "**/*.ts",
6 | parser: "@typescript-eslint/parser",
7 | parserOptions: {
8 | tsconfigRootDir: rootDir,
9 | project: "./tsconfig.json",
10 | },
11 | plugins: ["prettier", "@typescript-eslint"],
12 | extends: [
13 | "eslint:recommended",
14 | "plugin:@typescript-eslint/eslint-recommended",
15 | "plugin:@typescript-eslint/recommended",
16 | "plugin:prettier/recommended",
17 | "plugin:@typescript-eslint/recommended-requiring-type-checking",
18 | ],
19 | rules: {
20 | "@typescript-eslint/explicit-function-return-type": "off",
21 | },
22 | },
23 | {
24 | files: ["./*.js"],
25 | extends: [
26 | "eslint:recommended",
27 | "plugin:prettier/recommended",
28 | "plugin:node/recommended-script",
29 | ],
30 | rules: {
31 | "node/no-extraneous-require": [
32 | "error",
33 | {
34 | allowModules: ["@gitpkg/common"],
35 | },
36 | ],
37 | },
38 | },
39 | ],
40 | });
41 |
--------------------------------------------------------------------------------
/tools/common/eslint/node.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | root: true,
3 | extends: [
4 | "eslint:recommended",
5 | "plugin:prettier/recommended",
6 | "plugin:node/recommended",
7 | ],
8 | parserOptions: {
9 | ecmaVersion: 2020,
10 | },
11 | });
12 |
--------------------------------------------------------------------------------
/tools/common/jest/index.js:
--------------------------------------------------------------------------------
1 | module.exports = () => ({
2 | preset: "ts-jest",
3 | testEnvironment: "node",
4 | globals: {
5 | "ts-jest": {
6 | babelConfig: true,
7 | },
8 | },
9 | });
10 |
--------------------------------------------------------------------------------
/tools/common/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@gitpkg/common",
3 | "private": true,
4 | "version": "1.0.0",
5 | "main": "index.js",
6 | "license": "MIT",
7 | "engines": {
8 | "node": ">= 6"
9 | },
10 | "scripts": {
11 | "lint:fix": "eslint --cache --max-warnings 0 --fix"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/tools/common/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | /* Basic Options */
4 | // "incremental": true, /* Enable incremental compilation */
5 | "target": "es2019" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
6 | "module": "esnext" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
7 | // "lib": [], /* Specify library files to be included in the compilation. */
8 | // "allowJs": true, /* Allow javascript files to be compiled. */
9 | // "checkJs": true, /* Report errors in .js files. */
10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */
12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
13 | // "sourceMap": true, /* Generates corresponding '.map' file. */
14 | // "outFile": "./", /* Concatenate and emit output to single file. */
15 | // "outDir": "./", /* Redirect output structure to the directory. */
16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
17 | // "composite": true, /* Enable project compilation */
18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
19 | // "removeComments": true, /* Do not emit comments to output. */
20 | // "noEmit": true, /* Do not emit outputs. */
21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */
22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
24 |
25 | /* Strict Type-Checking Options */
26 | "strict": true /* Enable all strict type-checking options. */,
27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
28 | // "strictNullChecks": true, /* Enable strict null checks. */
29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */
30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
34 |
35 | /* Additional Checks */
36 | // "noUnusedLocals": true, /* Report errors on unused locals. */
37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */
38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
40 |
41 | /* Module Resolution Options */
42 | "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
46 | // "typeRoots": [], /* List of folders to include type definitions from. */
47 | // "types": [], /* Type declaration files to be included in compilation. */
48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
49 | // "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
52 |
53 | /* Source Map Options */
54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
58 |
59 | /* Experimental Options */
60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
62 |
63 | /* Advanced Options */
64 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
65 | },
66 | "include": []
67 | }
68 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@gitpkg/common/tsconfig",
3 | "include": ["api/**/*.ts"]
4 | }
5 |
--------------------------------------------------------------------------------