@sky.ui.pro/core 0.0.2 → 0.0.4
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/LICENSE.md +212 -212
- package/README.md +96 -94
- package/package.json +23 -12
- package/scripts/merge-manifest.mjs +81 -0
- package/scripts/postinstall.mjs +149 -61
- package/scripts/project-env.mjs +150 -0
- package/scripts/token.mjs +147 -0
package/package.json
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sky.ui.pro/core",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "Sky UI Pro web components
|
|
3
|
+
"version": "0.0.4",
|
|
4
|
+
"description": "Sky UI Pro web components — installs full package via postinstall (online license).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
7
|
-
"sideEffects": true,
|
|
8
7
|
"main": "./dist/stub/index.js",
|
|
9
|
-
"types": "./dist/stub/index.d.ts",
|
|
10
8
|
"exports": {
|
|
11
|
-
".":
|
|
12
|
-
"import": "./dist/stub/index.js",
|
|
13
|
-
"types": "./dist/stub/index.d.ts"
|
|
14
|
-
},
|
|
15
|
-
"./package.json": "./package.json"
|
|
9
|
+
".": "./dist/stub/index.js"
|
|
16
10
|
},
|
|
17
11
|
"files": [
|
|
18
12
|
"scripts/postinstall.mjs",
|
|
13
|
+
"scripts/token.mjs",
|
|
19
14
|
"dist/stub",
|
|
20
15
|
"README.md",
|
|
21
|
-
"LICENSE.md"
|
|
16
|
+
"LICENSE.md",
|
|
17
|
+
"scripts/project-env.mjs",
|
|
18
|
+
"scripts/merge-manifest.mjs"
|
|
22
19
|
],
|
|
23
20
|
"scripts": {
|
|
24
21
|
"postinstall": "node ./scripts/postinstall.mjs"
|
|
@@ -27,9 +24,14 @@
|
|
|
27
24
|
"tar": "^7.4.3"
|
|
28
25
|
},
|
|
29
26
|
"peerDependencies": {
|
|
30
|
-
"@sky.ui/core": "^0.0.
|
|
27
|
+
"@sky.ui/core": "^0.0.3",
|
|
31
28
|
"lit": ">=3 <4"
|
|
32
29
|
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"lit": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
33
35
|
"skyUiPro": {
|
|
34
36
|
"apiBase": "https://library.sky-ui.com",
|
|
35
37
|
"artifactRegistry": "gitlab",
|
|
@@ -38,5 +40,14 @@
|
|
|
38
40
|
"publishConfig": {
|
|
39
41
|
"access": "public",
|
|
40
42
|
"registry": "https://registry.npmjs.org/"
|
|
41
|
-
}
|
|
43
|
+
},
|
|
44
|
+
"repository": {
|
|
45
|
+
"type": "git",
|
|
46
|
+
"url": "git+https://gitlab.com/sky_ui/sky-ui.git",
|
|
47
|
+
"directory": "packages/pro-npm/core"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://gitlab.com/sky_ui/sky-ui/-/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://library.sky-ui.com"
|
|
42
53
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Merge public npm stub manifest with GitLab artifact manifest after postinstall hydration.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const ARTIFACT_MANIFEST_SIDECAR = ".sky-ui-pro-artifact-manifest.json";
|
|
6
|
+
|
|
7
|
+
const PRO_ARTIFACT_PREFIX = "@sky.ui.pro-artifact/";
|
|
8
|
+
const PRO_PUBLIC_PREFIX = "@sky.ui.pro/";
|
|
9
|
+
|
|
10
|
+
/** Rename @sky.ui.pro-artifact/* deps to @sky.ui.pro/* for installed packages. */
|
|
11
|
+
export function renameProArtifactDeps(deps) {
|
|
12
|
+
if (!deps || typeof deps !== "object") return deps;
|
|
13
|
+
const out = { ...deps };
|
|
14
|
+
for (const [key, value] of Object.entries(out)) {
|
|
15
|
+
if (key.startsWith(PRO_ARTIFACT_PREFIX)) {
|
|
16
|
+
const next = `${PRO_PUBLIC_PREFIX}${key.slice(PRO_ARTIFACT_PREFIX.length)}`;
|
|
17
|
+
if (!(next in out)) out[next] = value;
|
|
18
|
+
delete out[key];
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** True when exports already point at hydrated artifact paths (not stub-only). */
|
|
25
|
+
export function hasHydratedExports(manifest) {
|
|
26
|
+
const exp = manifest?.exports;
|
|
27
|
+
if (!exp || typeof exp !== "object") return false;
|
|
28
|
+
if (exp["./vite"] || exp["./webpack"] || exp["./rollup"] || exp["./license"]) return true;
|
|
29
|
+
const root = exp["."];
|
|
30
|
+
if (typeof root === "string") {
|
|
31
|
+
return root.includes("dist/index") || (root.includes("/dist/") && !root.includes("/stub/"));
|
|
32
|
+
}
|
|
33
|
+
if (root && typeof root === "object") {
|
|
34
|
+
const imp = root.import ?? root.default ?? root.module;
|
|
35
|
+
return typeof imp === "string" && imp.includes("dist/index");
|
|
36
|
+
}
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Build installed package.json: artifact resolution surface + public stub identity.
|
|
42
|
+
* @param {Record<string, unknown>} stub
|
|
43
|
+
* @param {Record<string, unknown>} artifact
|
|
44
|
+
*/
|
|
45
|
+
export function mergeHydratedManifest(stub, artifact) {
|
|
46
|
+
const stubDeps = stub.dependencies && typeof stub.dependencies === "object" ? stub.dependencies : {};
|
|
47
|
+
const tarVersion = stubDeps.tar;
|
|
48
|
+
|
|
49
|
+
const merged = {
|
|
50
|
+
name: stub.name,
|
|
51
|
+
version: stub.version,
|
|
52
|
+
description: stub.description ?? artifact.description,
|
|
53
|
+
type: stub.type ?? artifact.type,
|
|
54
|
+
license: stub.license ?? artifact.license,
|
|
55
|
+
main: artifact.main,
|
|
56
|
+
module: artifact.module,
|
|
57
|
+
types: artifact.types,
|
|
58
|
+
sideEffects: artifact.sideEffects,
|
|
59
|
+
exports: artifact.exports,
|
|
60
|
+
peerDependencies: {
|
|
61
|
+
...renameProArtifactDeps(artifact.peerDependencies),
|
|
62
|
+
...(stub.peerDependencies ?? {}),
|
|
63
|
+
},
|
|
64
|
+
dependencies: {
|
|
65
|
+
...renameProArtifactDeps(artifact.dependencies),
|
|
66
|
+
...(tarVersion ? { tar: tarVersion } : {}),
|
|
67
|
+
},
|
|
68
|
+
scripts: stub.scripts?.postinstall ? { postinstall: stub.scripts.postinstall } : {},
|
|
69
|
+
skyUiPro: stub.skyUiPro,
|
|
70
|
+
publishConfig: stub.publishConfig,
|
|
71
|
+
repository: stub.repository,
|
|
72
|
+
bugs: stub.bugs,
|
|
73
|
+
homepage: stub.homepage,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
if (artifact.peerDependenciesMeta && typeof artifact.peerDependenciesMeta === "object") {
|
|
77
|
+
merged.peerDependenciesMeta = artifact.peerDependenciesMeta;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return merged;
|
|
81
|
+
}
|
package/scripts/postinstall.mjs
CHANGED
|
@@ -1,37 +1,47 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
3
|
* Downloads the full @sky.ui.pro/* artifact from the Sky UI npm proxy (GitLab upstream).
|
|
4
|
-
*
|
|
5
|
-
* does not remove artifacts already on disk.
|
|
4
|
+
* Cache restore re-validates the access token; install fails closed without a valid token.
|
|
6
5
|
*/
|
|
7
6
|
import { createWriteStream } from "node:fs";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
7
|
+
import { cp, mkdir, mkdtemp, readdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
10
9
|
import { dirname, join } from "node:path";
|
|
11
10
|
import { fileURLToPath } from "node:url";
|
|
12
11
|
import { pipeline } from "node:stream/promises";
|
|
13
12
|
import { extract } from "tar";
|
|
13
|
+
import {
|
|
14
|
+
ARTIFACT_MANIFEST_SIDECAR,
|
|
15
|
+
hasHydratedExports,
|
|
16
|
+
mergeHydratedManifest,
|
|
17
|
+
} from "./merge-manifest.mjs";
|
|
18
|
+
import {
|
|
19
|
+
ENTITLEMENT_MARKER,
|
|
20
|
+
cacheRoot,
|
|
21
|
+
exists,
|
|
22
|
+
isInstallStrict,
|
|
23
|
+
legacyCacheRoot,
|
|
24
|
+
loadInstallProjectEnv,
|
|
25
|
+
resolveApiBase,
|
|
26
|
+
resolveToken,
|
|
27
|
+
validateAccessTokenOnline,
|
|
28
|
+
writeEntitlementMarker,
|
|
29
|
+
formatRenewalEmailHint,
|
|
30
|
+
} from "./token.mjs";
|
|
14
31
|
|
|
15
32
|
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
|
16
33
|
const pkgRoot = join(scriptDir, "..");
|
|
17
|
-
const SKY_UI_HOME = join(homedir(), ".sky.ui");
|
|
18
|
-
const LEGACY_SKY_UI_HOME = join(homedir(), ".sky-ui");
|
|
19
|
-
const cacheRoot = join(SKY_UI_HOME, "cache");
|
|
20
|
-
const legacyCacheRoot = join(LEGACY_SKY_UI_HOME, "cache");
|
|
21
34
|
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
await access(p);
|
|
25
|
-
return true;
|
|
26
|
-
} catch {
|
|
27
|
-
return false;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
35
|
+
const SKIP_COPY_TO_INSTALL = new Set([ENTITLEMENT_MARKER, ARTIFACT_MANIFEST_SIDECAR]);
|
|
30
36
|
|
|
31
37
|
async function readJson(p) {
|
|
32
38
|
return JSON.parse(await readFile(p, "utf8"));
|
|
33
39
|
}
|
|
34
40
|
|
|
41
|
+
async function writeJson(p, data) {
|
|
42
|
+
await writeFile(p, `${JSON.stringify(data, null, 2)}\n`, "utf8");
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
function packageSlug(name) {
|
|
36
46
|
return name.replace(/^@/, "").replace("/", "-");
|
|
37
47
|
}
|
|
@@ -40,9 +50,10 @@ function cacheVersionDir(root, slug, version) {
|
|
|
40
50
|
return join(root, slug, version);
|
|
41
51
|
}
|
|
42
52
|
|
|
43
|
-
async function copyDirectoryContents(src, dest) {
|
|
53
|
+
async function copyDirectoryContents(src, dest, { skip = SKIP_COPY_TO_INSTALL } = {}) {
|
|
44
54
|
await mkdir(dest, { recursive: true });
|
|
45
55
|
for (const entry of await readdir(src, { withFileTypes: true })) {
|
|
56
|
+
if (skip.has(entry.name)) continue;
|
|
46
57
|
await cp(join(src, entry.name), join(dest, entry.name), { recursive: true, force: true });
|
|
47
58
|
}
|
|
48
59
|
}
|
|
@@ -57,52 +68,81 @@ async function enforceCachePolicy(slug, version) {
|
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
70
|
|
|
60
|
-
async function
|
|
71
|
+
async function readArtifactManifestSidecar(cacheDir) {
|
|
72
|
+
const sidecar = join(cacheDir, ARTIFACT_MANIFEST_SIDECAR);
|
|
73
|
+
if (!(await exists(sidecar))) return null;
|
|
74
|
+
return readJson(sidecar);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Read artifact package.json from cached tarball (legacy caches without sidecar). */
|
|
78
|
+
async function readArtifactManifestFromTarball(slug, version) {
|
|
79
|
+
const tgz = join(cacheRoot, `${slug}-${version}.tgz`);
|
|
80
|
+
if (!(await exists(tgz))) return null;
|
|
81
|
+
const stagingDir = await mkdtemp(join(tmpdir(), `sky.ui.pro-manifest-${slug}-${version}-`));
|
|
82
|
+
try {
|
|
83
|
+
await extract({ file: tgz, cwd: stagingDir, strip: 1 });
|
|
84
|
+
return readJson(join(stagingDir, "package.json"));
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
} finally {
|
|
88
|
+
await rm(stagingDir, { recursive: true, force: true });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function resolveArtifactManifest(stubManifest, slug, version, cacheDir) {
|
|
93
|
+
const sidecar = cacheDir ? await readArtifactManifestSidecar(cacheDir) : null;
|
|
94
|
+
if (sidecar) return sidecar;
|
|
95
|
+
return readArtifactManifestFromTarball(slug, version);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function applyHydratedManifest(targetRoot, stubManifest, artifactManifest) {
|
|
99
|
+
const merged = mergeHydratedManifest(stubManifest, artifactManifest);
|
|
100
|
+
await writeJson(join(targetRoot, "package.json"), merged);
|
|
101
|
+
return merged;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function finalizeHydratedManifest(targetRoot, stubManifest, slug, version, cacheDir) {
|
|
105
|
+
const current = await readJson(join(targetRoot, "package.json"));
|
|
106
|
+
if (hasHydratedExports(current)) {
|
|
107
|
+
return current;
|
|
108
|
+
}
|
|
109
|
+
const artifactManifest = await resolveArtifactManifest(stubManifest, slug, version, cacheDir);
|
|
110
|
+
if (!artifactManifest) return current;
|
|
111
|
+
return applyHydratedManifest(targetRoot, stubManifest, artifactManifest);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function restoreFromCache(slug, version, target, stubManifest) {
|
|
61
115
|
for (const root of [cacheRoot, legacyCacheRoot]) {
|
|
62
116
|
const src = cacheVersionDir(root, slug, version);
|
|
63
117
|
if (!(await exists(src))) continue;
|
|
64
118
|
const entries = await readdir(src);
|
|
65
119
|
if (entries.length === 0) continue;
|
|
66
120
|
await copyDirectoryContents(src, target);
|
|
67
|
-
|
|
121
|
+
await finalizeHydratedManifest(target, stubManifest, slug, version, src);
|
|
122
|
+
return { cacheDir: src };
|
|
68
123
|
}
|
|
69
|
-
return
|
|
124
|
+
return null;
|
|
70
125
|
}
|
|
71
126
|
|
|
72
|
-
async function saveToCache(slug, version, source) {
|
|
127
|
+
async function saveToCache(slug, version, source, artifactManifest) {
|
|
73
128
|
const dest = cacheVersionDir(cacheRoot, slug, version);
|
|
74
129
|
await rm(dest, { recursive: true, force: true });
|
|
75
130
|
await mkdir(dest, { recursive: true });
|
|
76
131
|
await copyDirectoryContents(source, dest);
|
|
132
|
+
if (artifactManifest) {
|
|
133
|
+
await writeJson(join(dest, ARTIFACT_MANIFEST_SIDECAR), artifactManifest);
|
|
134
|
+
}
|
|
77
135
|
}
|
|
78
136
|
|
|
79
|
-
async function
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
for (const credPath of [
|
|
85
|
-
join(SKY_UI_HOME, "credentials.json"),
|
|
86
|
-
join(LEGACY_SKY_UI_HOME, "credentials.json"),
|
|
87
|
-
]) {
|
|
88
|
-
if (!(await exists(credPath))) continue;
|
|
89
|
-
const creds = JSON.parse(await readFile(credPath, "utf8"));
|
|
90
|
-
if (typeof creds.personalToken === "string" && creds.personalToken.trim()) {
|
|
91
|
-
return creds.personalToken.trim();
|
|
92
|
-
}
|
|
93
|
-
if (typeof creds.authToken === "string" && creds.authToken.trim()) {
|
|
94
|
-
return creds.authToken.trim();
|
|
137
|
+
async function invalidateCache(slug, version) {
|
|
138
|
+
for (const root of [cacheRoot, legacyCacheRoot]) {
|
|
139
|
+
const dir = cacheVersionDir(root, slug, version);
|
|
140
|
+
if (await exists(dir)) {
|
|
141
|
+
await rm(dir, { recursive: true, force: true });
|
|
95
142
|
}
|
|
96
143
|
}
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function resolveApiBase(pkg) {
|
|
101
|
-
const fromEnv = process.env.SKY_UI_API_URL?.trim();
|
|
102
|
-
if (fromEnv) return fromEnv.replace(/\/api\/?$/, "").replace(/\/$/, "");
|
|
103
|
-
const fromPkg = pkg?.skyUiPro?.apiBase?.trim();
|
|
104
|
-
if (fromPkg) return fromPkg.replace(/\/$/, "");
|
|
105
|
-
return "https://library.sky-ui.com";
|
|
144
|
+
const tgz = join(cacheRoot, `${slug}-${version}.tgz`);
|
|
145
|
+
if (await exists(tgz)) await rm(tgz, { force: true });
|
|
106
146
|
}
|
|
107
147
|
|
|
108
148
|
function tarballFileName(name, version) {
|
|
@@ -116,7 +156,7 @@ function tarballUrl(apiBase, name, version) {
|
|
|
116
156
|
return `${base}/npm/${scopePath}/-/${file}`;
|
|
117
157
|
}
|
|
118
158
|
|
|
119
|
-
function printMissingToken(name) {
|
|
159
|
+
function printMissingToken(name, apiBase) {
|
|
120
160
|
console.warn(`
|
|
121
161
|
[@sky.ui.pro] Missing install token — ${name} was not downloaded.
|
|
122
162
|
|
|
@@ -127,8 +167,26 @@ function printMissingToken(name) {
|
|
|
127
167
|
CI / pipelines:
|
|
128
168
|
export SKY_UI_AUTH_TOKEN=sk_ci_…
|
|
129
169
|
|
|
130
|
-
|
|
170
|
+
Or add SKY_UI_PERSONAL_TOKEN / SKY_UI_AUTH_TOKEN to your project .env
|
|
171
|
+
API host (SKY_UI_API_URL): ${apiBase}
|
|
172
|
+
|
|
173
|
+
Then re-run: npm install "${name}"
|
|
131
174
|
Docs: https://library.sky-ui.com/docs/getting-started
|
|
175
|
+
|
|
176
|
+
Maintainer escape hatch: SKY_UI_PRO_LENIENT=1
|
|
177
|
+
`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function printInvalidToken(name, result, apiBase) {
|
|
181
|
+
console.error(
|
|
182
|
+
`[@sky.ui.pro] License validation failed for ${name}: ${result.status ?? "inactive"}${
|
|
183
|
+
result.error ? ` — ${result.error}` : ""
|
|
184
|
+
}`,
|
|
185
|
+
);
|
|
186
|
+
console.error(`[@sky.ui.pro] API host: ${apiBase}`);
|
|
187
|
+
const renewalHint = formatRenewalEmailHint(result);
|
|
188
|
+
console.error(`
|
|
189
|
+
Renew or regenerate your token, then re-run: npm install "${name}"${renewalHint}
|
|
132
190
|
`);
|
|
133
191
|
}
|
|
134
192
|
|
|
@@ -136,10 +194,10 @@ async function installFromTarball(tgzPath, slug, version, stubManifest) {
|
|
|
136
194
|
const stagingDir = await mkdtemp(join(tmpdir(), `sky.ui.pro-${slug}-${version}-`));
|
|
137
195
|
try {
|
|
138
196
|
await extract({ file: tgzPath, cwd: stagingDir, strip: 1 });
|
|
197
|
+
const artifactManifest = await readJson(join(stagingDir, "package.json"));
|
|
139
198
|
await copyDirectoryContents(stagingDir, pkgRoot);
|
|
140
|
-
|
|
141
|
-
await
|
|
142
|
-
await saveToCache(slug, version, pkgRoot);
|
|
199
|
+
await applyHydratedManifest(pkgRoot, stubManifest, artifactManifest);
|
|
200
|
+
await saveToCache(slug, version, pkgRoot, artifactManifest);
|
|
143
201
|
} finally {
|
|
144
202
|
await rm(stagingDir, { recursive: true, force: true });
|
|
145
203
|
}
|
|
@@ -148,6 +206,8 @@ async function installFromTarball(tgzPath, slug, version, stubManifest) {
|
|
|
148
206
|
async function main() {
|
|
149
207
|
if (process.env.SKY_UI_PRO_SKIP_POSTINSTALL === "1") return;
|
|
150
208
|
|
|
209
|
+
await loadInstallProjectEnv(pkgRoot);
|
|
210
|
+
|
|
151
211
|
const pkg = await readJson(join(pkgRoot, "package.json"));
|
|
152
212
|
const { name, version } = pkg;
|
|
153
213
|
if (!name?.startsWith("@sky.ui.pro/") || !version) {
|
|
@@ -156,25 +216,52 @@ async function main() {
|
|
|
156
216
|
}
|
|
157
217
|
|
|
158
218
|
const stubManifest = structuredClone(pkg);
|
|
159
|
-
|
|
160
219
|
const slug = packageSlug(name);
|
|
220
|
+
const apiBase = resolveApiBase(pkg);
|
|
221
|
+
const strict = isInstallStrict();
|
|
222
|
+
|
|
161
223
|
await enforceCachePolicy(slug, version);
|
|
162
224
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
225
|
+
const token = await resolveToken();
|
|
226
|
+
const cacheAvailable =
|
|
227
|
+
(await exists(cacheVersionDir(cacheRoot, slug, version))) ||
|
|
228
|
+
(await exists(cacheVersionDir(legacyCacheRoot, slug, version)));
|
|
229
|
+
|
|
230
|
+
if (cacheAvailable) {
|
|
231
|
+
if (!token) {
|
|
232
|
+
printMissingToken(name, apiBase);
|
|
233
|
+
if (strict) process.exit(1);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const validation = await validateAccessTokenOnline(token, apiBase);
|
|
237
|
+
if (validation.active) {
|
|
238
|
+
if (await restoreFromCache(slug, version, pkgRoot, stubManifest)) {
|
|
239
|
+
await writeEntitlementMarker(pkgRoot, { name, version, status: validation.status ?? "active" });
|
|
240
|
+
console.log(`[@sky.ui.pro] Restored ${name}@${version} from cache.`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
printInvalidToken(name, validation, apiBase);
|
|
245
|
+
await invalidateCache(slug, version);
|
|
246
|
+
if (strict) process.exit(1);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
166
249
|
}
|
|
167
250
|
|
|
168
|
-
const token = await resolveToken();
|
|
169
251
|
if (!token) {
|
|
170
|
-
printMissingToken(name);
|
|
171
|
-
if (process.
|
|
172
|
-
|
|
173
|
-
|
|
252
|
+
printMissingToken(name, apiBase);
|
|
253
|
+
if (strict) process.exit(1);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const preDownloadValidation = await validateAccessTokenOnline(token, apiBase);
|
|
258
|
+
if (!preDownloadValidation.active) {
|
|
259
|
+
printInvalidToken(name, preDownloadValidation, apiBase);
|
|
260
|
+
if (strict) process.exit(1);
|
|
174
261
|
return;
|
|
175
262
|
}
|
|
176
263
|
|
|
177
|
-
const url = tarballUrl(
|
|
264
|
+
const url = tarballUrl(apiBase, name, version);
|
|
178
265
|
const res = await fetch(url, {
|
|
179
266
|
headers: { Authorization: `Bearer ${token}`, Accept: "application/octet-stream" },
|
|
180
267
|
});
|
|
@@ -190,6 +277,7 @@ async function main() {
|
|
|
190
277
|
const tgzPath = join(cacheRoot, `${slug}-${version}.tgz`);
|
|
191
278
|
await pipeline(res.body, createWriteStream(tgzPath));
|
|
192
279
|
await installFromTarball(tgzPath, slug, version, stubManifest);
|
|
280
|
+
await writeEntitlementMarker(pkgRoot, { name, version, status: "active" });
|
|
193
281
|
console.log(`[@sky.ui.pro] Installed ${name}@${version}`);
|
|
194
282
|
}
|
|
195
283
|
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Unified Sky UI Pro / CLI project `.env` loading.
|
|
4
|
+
* Source of truth — synced to @sky.ui/cli and @sky.ui.pro/* postinstall scripts before publish.
|
|
5
|
+
*
|
|
6
|
+
* Loads only `SKY_UI_*` keys. Shell env always wins (existing process.env is never overwritten).
|
|
7
|
+
* Search order: INIT_CWD → npm local prefix → SKY_UI_PROJECT_ROOT → cwd → project roots.
|
|
8
|
+
*/
|
|
9
|
+
import { access, readFile } from "node:fs/promises";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
|
|
12
|
+
export const SKY_UI_ENV_PREFIX = "SKY_UI_";
|
|
13
|
+
|
|
14
|
+
/** Filenames tried in each project directory (later files override earlier within the same dir). */
|
|
15
|
+
export const SKY_UI_ENV_FILES = [".env", ".env.local"];
|
|
16
|
+
|
|
17
|
+
/** Documented variables (for README / tooling — not enforced here). */
|
|
18
|
+
export const SKY_UI_ENV_VARS = [
|
|
19
|
+
"SKY_UI_API_URL",
|
|
20
|
+
"SKY_UI_AUTH_TOKEN",
|
|
21
|
+
"SKY_UI_PERSONAL_TOKEN",
|
|
22
|
+
"SKY_UI_PROJECT_ROOT",
|
|
23
|
+
"SKY_UI_PRO_FRAMEWORK",
|
|
24
|
+
"SKY_UI_PRO_LENIENT",
|
|
25
|
+
"SKY_UI_PRO_LICENSE_LENIENT",
|
|
26
|
+
"SKY_UI_PRO_SKIP_POSTINSTALL",
|
|
27
|
+
"SKY_UI_PRO_STRICT",
|
|
28
|
+
"SKY_UI_NPM_REGISTRY",
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
export async function pathExists(p) {
|
|
32
|
+
try {
|
|
33
|
+
await access(p);
|
|
34
|
+
return true;
|
|
35
|
+
} catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeDirKey(dir) {
|
|
41
|
+
return dir.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function shouldLoadKey(key) {
|
|
45
|
+
return key.startsWith(SKY_UI_ENV_PREFIX);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function envAlreadySet(key) {
|
|
49
|
+
const v = process.env[key];
|
|
50
|
+
return typeof v === "string" && v.trim().length > 0;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function unquote(value) {
|
|
54
|
+
if (
|
|
55
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
56
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
57
|
+
) {
|
|
58
|
+
return value.slice(1, -1);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Parse and apply one `.env` file. Does not overwrite non-empty process.env values. */
|
|
64
|
+
export async function loadEnvFile(envFilePath) {
|
|
65
|
+
try {
|
|
66
|
+
const raw = await readFile(envFilePath, "utf8");
|
|
67
|
+
for (const line of raw.split(/\r?\n/)) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
70
|
+
const eq = trimmed.indexOf("=");
|
|
71
|
+
if (eq <= 0) continue;
|
|
72
|
+
const key = trimmed.slice(0, eq).trim();
|
|
73
|
+
if (!shouldLoadKey(key)) continue;
|
|
74
|
+
if (envAlreadySet(key)) continue;
|
|
75
|
+
process.env[key] = unquote(trimmed.slice(eq + 1).trim());
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
/* missing or unreadable */
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Load `.env` (+ `.env.local`) from a project directory. */
|
|
83
|
+
export async function loadProjectEnv(cwd = process.cwd()) {
|
|
84
|
+
for (const name of SKY_UI_ENV_FILES) {
|
|
85
|
+
await loadEnvFile(join(cwd, name));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Walk up to find the nearest directory containing `package.json`. */
|
|
90
|
+
export async function findProjectRoot(startDir, maxDepth = 12) {
|
|
91
|
+
let dir = startDir;
|
|
92
|
+
for (let i = 0; i < maxDepth; i++) {
|
|
93
|
+
if (await pathExists(join(dir, "package.json"))) return dir;
|
|
94
|
+
const parent = dirname(dir);
|
|
95
|
+
if (parent === dir) break;
|
|
96
|
+
dir = parent;
|
|
97
|
+
}
|
|
98
|
+
return startDir;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Directories to scan for `.env`, in priority order (first match wins per key;
|
|
103
|
+
* later dirs only fill keys still unset).
|
|
104
|
+
*/
|
|
105
|
+
export async function collectEnvSearchDirs(options = {}) {
|
|
106
|
+
const { pkgRoot, extraDirs = [] } = options;
|
|
107
|
+
const seen = new Set();
|
|
108
|
+
const ordered = [];
|
|
109
|
+
|
|
110
|
+
const push = (dir) => {
|
|
111
|
+
if (!dir || typeof dir !== "string") return;
|
|
112
|
+
const key = normalizeDirKey(dir);
|
|
113
|
+
if (seen.has(key)) return;
|
|
114
|
+
seen.add(key);
|
|
115
|
+
ordered.push(dir);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
push(process.env.INIT_CWD);
|
|
119
|
+
push(process.env.npm_config_local_prefix);
|
|
120
|
+
push(process.env.SKY_UI_PROJECT_ROOT?.trim());
|
|
121
|
+
push(process.cwd());
|
|
122
|
+
|
|
123
|
+
const cwdRoot = await findProjectRoot(process.cwd());
|
|
124
|
+
push(cwdRoot);
|
|
125
|
+
|
|
126
|
+
if (pkgRoot) {
|
|
127
|
+
push(await findProjectRoot(dirname(pkgRoot)));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
for (const dir of extraDirs) {
|
|
131
|
+
push(dir);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return ordered;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Load Sky UI env from consumer project `.env` files.
|
|
139
|
+
* @param {{ pkgRoot?: string, extraDirs?: string[] }} [options]
|
|
140
|
+
*/
|
|
141
|
+
export async function loadConsumerProjectEnv(options = {}) {
|
|
142
|
+
for (const dir of await collectEnvSearchDirs(options)) {
|
|
143
|
+
await loadProjectEnv(dir);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** @deprecated Alias used by postinstall — prefer loadConsumerProjectEnv */
|
|
148
|
+
export async function loadInstallProjectEnv(pkgRoot) {
|
|
149
|
+
return loadConsumerProjectEnv({ pkgRoot });
|
|
150
|
+
}
|