shiply-cli 0.14.3 → 0.14.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/dist/claim.js +17 -5
- package/dist/domain.js +64 -31
- package/dist/index.js +3 -0
- package/dist/psl.data.js +6943 -0
- package/dist/publish.js +1 -0
- package/package.json +1 -1
- package/skill/SKILL.md +10 -1
package/README.md
CHANGED
|
@@ -3,9 +3,17 @@
|
|
|
3
3
|
Publish static sites to [shiply.now](https://shiply.now) from the command line —
|
|
4
4
|
instant web hosting built for agents.
|
|
5
5
|
|
|
6
|
+
> **The npm package is `shiply-cli`, not `shiply`.** A different npm package
|
|
7
|
+
> named `shiply` (an unrelated auto-commit watcher) is published by someone
|
|
8
|
+
> else — installing it does NOT give you the shiply.now CLI. Always use
|
|
9
|
+
> `shiply-cli`. The binary on PATH is still named `shiply` after install;
|
|
10
|
+
> the warning is only about the npm package name.
|
|
11
|
+
|
|
6
12
|
```bash
|
|
7
13
|
npm install -g shiply-cli
|
|
8
|
-
# or
|
|
14
|
+
# or, no install:
|
|
15
|
+
npx -y shiply-cli@latest publish ./dist
|
|
16
|
+
# or, install script:
|
|
9
17
|
curl -fsSL https://shiply.now/install.sh | bash
|
|
10
18
|
```
|
|
11
19
|
|
package/dist/claim.js
CHANGED
|
@@ -13,20 +13,32 @@ export async function runClaimVerify(args) {
|
|
|
13
13
|
message: 'No .shiply.json in current directory. Run from the directory where you published, or rerun `shiply publish`.',
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
|
-
if (!mf.
|
|
17
|
-
return { ok: false, message: '.shiply.json is missing
|
|
16
|
+
if (!mf.claimToken) {
|
|
17
|
+
return { ok: false, message: '.shiply.json is missing claimToken.' };
|
|
18
|
+
}
|
|
19
|
+
// Anonymous publishes prior to 0.14.4 didn't write siteId to .shiply.json,
|
|
20
|
+
// and the publish API didn't return it either. The server now accepts EITHER
|
|
21
|
+
// {siteId, claimToken} or {slug, claimToken} — fall back to slug when the
|
|
22
|
+
// local state file came from an older CLI.
|
|
23
|
+
if (!mf.siteId && !mf.slug) {
|
|
24
|
+
return { ok: false, message: '.shiply.json is missing siteId or slug.' };
|
|
18
25
|
}
|
|
19
26
|
if (!/^SHIPLY-[A-Z2-7]{8}$/.test(args.code)) {
|
|
20
27
|
return { ok: false, message: 'Invalid code format. Expected SHIPLY-XXXXXXXX.' };
|
|
21
28
|
}
|
|
29
|
+
const body = { claimToken: mf.claimToken };
|
|
30
|
+
if (mf.siteId)
|
|
31
|
+
body.siteId = mf.siteId;
|
|
32
|
+
else if (mf.slug)
|
|
33
|
+
body.slug = mf.slug;
|
|
22
34
|
const res = await fetch(`${origin}/api/v1/claim/pair/${encodeURIComponent(args.code)}/verify`, {
|
|
23
35
|
method: 'POST',
|
|
24
36
|
headers: { 'content-type': 'application/json' },
|
|
25
|
-
body: JSON.stringify(
|
|
37
|
+
body: JSON.stringify(body),
|
|
26
38
|
});
|
|
27
39
|
if (!res.ok) {
|
|
28
|
-
const
|
|
29
|
-
return { ok: false, message: `verify failed: ${
|
|
40
|
+
const errBody = (await res.json().catch(() => ({})));
|
|
41
|
+
return { ok: false, message: `verify failed: ${errBody.error?.message ?? res.status}` };
|
|
30
42
|
}
|
|
31
43
|
return { ok: true, message: 'Verified — switch back to your browser to finish claiming.' };
|
|
32
44
|
}
|
package/dist/domain.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { api, resolveBase } from './publish.js';
|
|
2
|
+
import { PSL_RULES } from './psl.data.js';
|
|
2
3
|
const headers = (apiKey) => ({ 'content-type': 'application/json', authorization: `Bearer ${apiKey}` });
|
|
3
4
|
const enc = (d) => encodeURIComponent(d);
|
|
4
5
|
export async function domainLs(ctx) {
|
|
@@ -24,40 +25,72 @@ export async function domainConnect(ctx, domain) {
|
|
|
24
25
|
const r = await api(`${base}/api/v1/custom-domains/${enc(domain)}/connect`, { method: 'POST', headers: headers(ctx.apiKey), body: '{}' });
|
|
25
26
|
console.log(`Open this link in your browser to connect ${domain}:\n ${r.url}`);
|
|
26
27
|
}
|
|
27
|
-
/**
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
28
|
+
/** Index the PSL into three buckets for O(1) lookup. Exception rules ('!foo')
|
|
29
|
+
* win over wildcard rules ('*.foo') which win over plain rules ('foo'). */
|
|
30
|
+
const PSL_EXACT = new Set();
|
|
31
|
+
const PSL_WILDCARD = new Set();
|
|
32
|
+
const PSL_EXCEPTION = new Set();
|
|
33
|
+
for (const rule of PSL_RULES) {
|
|
34
|
+
if (rule.startsWith('!'))
|
|
35
|
+
PSL_EXCEPTION.add(rule.slice(1));
|
|
36
|
+
else if (rule.startsWith('*.'))
|
|
37
|
+
PSL_WILDCARD.add(rule.slice(2));
|
|
38
|
+
else
|
|
39
|
+
PSL_EXACT.add(rule);
|
|
40
|
+
}
|
|
41
|
+
/** Find the longest matching public suffix for a hostname, applying the full
|
|
42
|
+
* PSL algorithm: exception rules win, then wildcards, then longest exact
|
|
43
|
+
* match. Returns the suffix as a dotted string (e.g. 'com.au', 'co.uk').
|
|
44
|
+
* Returns null if no rule matches (hostname has only a single label, or it's
|
|
45
|
+
* IDN/punycode outside the ICANN list). */
|
|
46
|
+
export function findPublicSuffix(hostname) {
|
|
47
|
+
const labels = hostname.toLowerCase().split('.');
|
|
48
|
+
// Walk from most-specific (full hostname) to least-specific (TLD).
|
|
49
|
+
for (let i = 0; i < labels.length; i++) {
|
|
50
|
+
const candidate = labels.slice(i).join('.');
|
|
51
|
+
// 1. Exception rule wins → suffix is candidate minus its first label.
|
|
52
|
+
if (PSL_EXCEPTION.has(candidate)) {
|
|
53
|
+
return labels.slice(i + 1).join('.') || null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
for (let i = 0; i < labels.length; i++) {
|
|
57
|
+
const candidate = labels.slice(i).join('.');
|
|
58
|
+
// 2. Exact match.
|
|
59
|
+
if (PSL_EXACT.has(candidate))
|
|
60
|
+
return candidate;
|
|
61
|
+
// 3. Wildcard: parent of candidate (everything after candidate's first
|
|
62
|
+
// label) is in the wildcard set.
|
|
63
|
+
if (i + 1 < labels.length) {
|
|
64
|
+
const parent = labels.slice(i + 1).join('.');
|
|
65
|
+
if (PSL_WILDCARD.has(parent))
|
|
66
|
+
return candidate;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
/** Split hostname into (registrable domain, subdomain) using the full PSL.
|
|
72
|
+
* The registrable domain is the public suffix plus one extra label.
|
|
73
|
+
* E.g. `www.example.co.uk` → suffix `co.uk`, domain `example.co.uk`, sub `www`.
|
|
74
|
+
* `foo.city.kawasaki.jp` → exception `!city.kawasaki.jp` makes the suffix
|
|
75
|
+
* `kawasaki.jp`, so domain is `city.kawasaki.jp` and sub is `foo`.
|
|
76
|
+
* Falls back to the last-two-labels heuristic if the hostname is below the
|
|
77
|
+
* PSL coverage (no matching rule). */
|
|
50
78
|
export function splitRegistrable(hostname) {
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
const host = hostname.toLowerCase().replace(/\.$/, '');
|
|
80
|
+
const parts = host.split('.');
|
|
81
|
+
const suffix = findPublicSuffix(host);
|
|
82
|
+
if (suffix) {
|
|
83
|
+
const suffixLabels = suffix.split('.').length;
|
|
84
|
+
// Registrable domain = public suffix + one more label.
|
|
85
|
+
if (parts.length <= suffixLabels) {
|
|
86
|
+
// Hostname IS the public suffix — not registrable on its own.
|
|
87
|
+
return { domain: host, subdomain: '@' };
|
|
59
88
|
}
|
|
89
|
+
const domain = parts.slice(-(suffixLabels + 1)).join('.');
|
|
90
|
+
const subdomain = parts.slice(0, -(suffixLabels + 1)).join('.') || '@';
|
|
91
|
+
return { domain, subdomain };
|
|
60
92
|
}
|
|
93
|
+
// No PSL hit — fall back to last-two-labels.
|
|
61
94
|
const domain = parts.slice(-2).join('.');
|
|
62
95
|
const subdomain = parts.slice(0, -2).join('.') || '@';
|
|
63
96
|
return { domain, subdomain };
|
package/dist/index.js
CHANGED
|
@@ -256,6 +256,9 @@ async function main() {
|
|
|
256
256
|
await writeState(publishDir, {
|
|
257
257
|
slug: res.slug,
|
|
258
258
|
siteUrl: res.siteUrl,
|
|
259
|
+
// siteId only present on 0.14.4+ servers — prefer the new response,
|
|
260
|
+
// fall back to a prior state file so re-publishes don't drop it.
|
|
261
|
+
...(res.siteId ? { siteId: res.siteId } : state?.siteId ? { siteId: state.siteId } : {}),
|
|
259
262
|
...(res.claimToken ? { claimToken: res.claimToken } : state?.claimToken && updating ? { claimToken: state.claimToken } : {}),
|
|
260
263
|
owned: !res.anonymous,
|
|
261
264
|
...(state?.databaseId ? { databaseId: state.databaseId } : {}),
|