tines 0.0.150 → 0.0.152
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +122 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3774,6 +3774,64 @@ function requirementFix(r, ref) {
|
|
|
3774
3774
|
command: `tines issues artifacts delete ${ref} ${r.artifact} && ${attach(r.type ?? "file")}`
|
|
3775
3775
|
};
|
|
3776
3776
|
}
|
|
3777
|
+
function concreteContentType(ct) {
|
|
3778
|
+
return ct !== void 0 && ct.includes("/") && !ct.endsWith("/") ? ct : void 0;
|
|
3779
|
+
}
|
|
3780
|
+
function requirementAccepts(r, type, contentType) {
|
|
3781
|
+
if (r.type !== void 0 && r.type !== type) return false;
|
|
3782
|
+
if (r.content_type === void 0) return true;
|
|
3783
|
+
if (contentType === void 0) return true;
|
|
3784
|
+
return contentType.startsWith(r.content_type);
|
|
3785
|
+
}
|
|
3786
|
+
function declaredContentType(reqs, type) {
|
|
3787
|
+
if (type !== "text" && type !== "file") return void 0;
|
|
3788
|
+
const declared = [
|
|
3789
|
+
...new Set(
|
|
3790
|
+
reqs.filter((r) => r.type === type).map((r) => concreteContentType(r.content_type)).filter((ct) => ct !== void 0)
|
|
3791
|
+
)
|
|
3792
|
+
];
|
|
3793
|
+
return declared.length === 1 ? declared[0] : void 0;
|
|
3794
|
+
}
|
|
3795
|
+
|
|
3796
|
+
// ../shared/src/artifact-site.ts
|
|
3797
|
+
var ARTIFACT_SITE_LINK_TTL_MS = 60 * 60 * 1e3;
|
|
3798
|
+
var ARTIFACT_SITE_INDEX = "index.html";
|
|
3799
|
+
function siteEntry(type, contentType, files = []) {
|
|
3800
|
+
if (type === "file" || type === "text") {
|
|
3801
|
+
return (contentType ?? "").toLowerCase().startsWith("text/html") ? "" : null;
|
|
3802
|
+
}
|
|
3803
|
+
if (type === "folder") {
|
|
3804
|
+
return files.some((f) => f.path === ARTIFACT_SITE_INDEX) ? ARTIFACT_SITE_INDEX : null;
|
|
3805
|
+
}
|
|
3806
|
+
return null;
|
|
3807
|
+
}
|
|
3808
|
+
var VIEWPORT_META = /<meta[^>]+name\s*=\s*["']?viewport["']?[^>]*content\s*=\s*["'][^"']*width\s*=\s*device-width/i;
|
|
3809
|
+
var EXTERNAL_URL = /^(?:https?:)?\/\//i;
|
|
3810
|
+
function externalRefs(html) {
|
|
3811
|
+
const found = [];
|
|
3812
|
+
const push = (url) => {
|
|
3813
|
+
if (url && EXTERNAL_URL.test(url.trim()) && !found.includes(url.trim())) found.push(url.trim());
|
|
3814
|
+
};
|
|
3815
|
+
for (const m of html.matchAll(/<script[^>]*\ssrc\s*=\s*["']([^"']+)["']/gi)) push(m[1]);
|
|
3816
|
+
for (const m of html.matchAll(/<link[^>]*\shref\s*=\s*["']([^"']+)["']/gi)) push(m[1]);
|
|
3817
|
+
for (const m of html.matchAll(/@import\s+(?:url\()?\s*["']([^"']+)["']/gi)) push(m[1]);
|
|
3818
|
+
return found;
|
|
3819
|
+
}
|
|
3820
|
+
function lintHtmlArtifact(html) {
|
|
3821
|
+
const warnings = [];
|
|
3822
|
+
if (!VIEWPORT_META.test(html)) {
|
|
3823
|
+
warnings.push(
|
|
3824
|
+
'no viewport meta tag \u2014 add <meta name="viewport" content="width=device-width, initial-scale=1"> so it lays out for phones'
|
|
3825
|
+
);
|
|
3826
|
+
}
|
|
3827
|
+
const external = externalRefs(html);
|
|
3828
|
+
if (external.length > 0) {
|
|
3829
|
+
warnings.push(
|
|
3830
|
+
`external scripts/styles are blocked by the sandbox policy (${external.slice(0, 3).join(", ")}${external.length > 3 ? ", \u2026" : ""}) \u2014 inline or vendor them into the artifact`
|
|
3831
|
+
);
|
|
3832
|
+
}
|
|
3833
|
+
return warnings;
|
|
3834
|
+
}
|
|
3777
3835
|
|
|
3778
3836
|
// ../shared/src/client.ts
|
|
3779
3837
|
var ApiError = class extends Error {
|
|
@@ -3957,6 +4015,11 @@ function createApiClient(options) {
|
|
|
3957
4015
|
// Issue artifacts (name-addressed under the issue)
|
|
3958
4016
|
listArtifacts: (issueId) => get(`/api/v1/issues/${issueId}/artifacts`),
|
|
3959
4017
|
getArtifact: (issueId, name2) => get(artifactPath(issueId, name2)),
|
|
4018
|
+
/**
|
|
4019
|
+
* Mints a short-lived signed URL that renders an HTML artifact live
|
|
4020
|
+
* (422 `not_a_site` when the artifact is not HTML / has no index.html).
|
|
4021
|
+
*/
|
|
4022
|
+
createArtifactSiteLink: (issueId, name2, body = {}) => request("POST", artifactPath(issueId, name2, "/site-link"), body),
|
|
3960
4023
|
/** JSON upsert for text/link/pr: creates the artifact or appends a version. */
|
|
3961
4024
|
putArtifact: (issueId, name2, body) => request("PUT", artifactPath(issueId, name2), body),
|
|
3962
4025
|
/** Raw-body upload for `file`: creates the artifact or appends a version. */
|
|
@@ -4970,9 +5033,6 @@ function normalizePositional(raw) {
|
|
|
4970
5033
|
return { value: raw, stdin: false };
|
|
4971
5034
|
}
|
|
4972
5035
|
var isUrl = (v) => /^https?:\/\//.test(v);
|
|
4973
|
-
function concreteContentType(ct) {
|
|
4974
|
-
return ct !== void 0 && ct.includes("/") && !ct.endsWith("/") ? ct : void 0;
|
|
4975
|
-
}
|
|
4976
5036
|
function declaredTypes(gates) {
|
|
4977
5037
|
return [
|
|
4978
5038
|
...new Set(gates.map((g) => g.check.type).filter((t) => t !== void 0))
|
|
@@ -5000,13 +5060,6 @@ function joinTransitions(names) {
|
|
|
5000
5060
|
function fixCommand(gate, ref) {
|
|
5001
5061
|
return gate.check.fix || requirementFix(gate.check, ref).command;
|
|
5002
5062
|
}
|
|
5003
|
-
function accepts(gate, type, contentType) {
|
|
5004
|
-
const check = gate.check;
|
|
5005
|
-
if (check.type !== void 0 && check.type !== type) return false;
|
|
5006
|
-
if (check.content_type === void 0) return true;
|
|
5007
|
-
if (contentType === void 0) return true;
|
|
5008
|
-
return contentType.startsWith(check.content_type);
|
|
5009
|
-
}
|
|
5010
5063
|
function effectiveContentType(type, source, explicit, sniff) {
|
|
5011
5064
|
if (explicit !== void 0) return explicit;
|
|
5012
5065
|
if (type === "text") return "text/markdown";
|
|
@@ -5124,13 +5177,10 @@ function planPositional(name2, raw, gates, probe, flags) {
|
|
|
5124
5177
|
}
|
|
5125
5178
|
}
|
|
5126
5179
|
function gateContentType(gates, type) {
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
)
|
|
5132
|
-
];
|
|
5133
|
-
return declared.length === 1 ? declared[0] : void 0;
|
|
5180
|
+
return declaredContentType(
|
|
5181
|
+
gates.map((g) => g.check),
|
|
5182
|
+
type
|
|
5183
|
+
);
|
|
5134
5184
|
}
|
|
5135
5185
|
function planFlags(flags, probe) {
|
|
5136
5186
|
const ct = flags.contentType;
|
|
@@ -5183,7 +5233,7 @@ function checkExistingSlot(plan, gates, ref, name2, flags, positional) {
|
|
|
5183
5233
|
}
|
|
5184
5234
|
function checkAccepted(plan, gates, ref, name2, flags, positional, sniff) {
|
|
5185
5235
|
const effective = effectiveContentType(plan.type, plan.source, plan.contentType, sniff);
|
|
5186
|
-
if (gates.some((g2) =>
|
|
5236
|
+
if (gates.some((g2) => requirementAccepts(g2.check, plan.type, effective))) return;
|
|
5187
5237
|
const label = sourceLabel(flags, positional);
|
|
5188
5238
|
const typeMatched = gates.filter((g2) => (g2.check.type ?? plan.type) === plan.type);
|
|
5189
5239
|
if (typeMatched.length > 0) {
|
|
@@ -5202,7 +5252,11 @@ function satisfiedBy(artifact, gates) {
|
|
|
5202
5252
|
const rejects = [];
|
|
5203
5253
|
for (const gate of gates) {
|
|
5204
5254
|
if (gate.check.artifact !== artifact.name) continue;
|
|
5205
|
-
if (
|
|
5255
|
+
if (requirementAccepts(
|
|
5256
|
+
gate.check,
|
|
5257
|
+
artifact.artifact_type,
|
|
5258
|
+
artifact.current_version.content_type ?? void 0
|
|
5259
|
+
)) {
|
|
5206
5260
|
satisfies.push(gate.transition);
|
|
5207
5261
|
} else {
|
|
5208
5262
|
rejects.push({ transition: gate.transition, wants: wantsLabel(gate, artifact) });
|
|
@@ -5887,6 +5941,16 @@ skills: ${context.skills.map((s) => s.name).join(", ")}`);
|
|
|
5887
5941
|
`current: v${artifact.current_version.version} (${artifact.fresh ? "fresh" : "attached before the current state \u2014 reaffirm or attach a new version to satisfy gates"})`
|
|
5888
5942
|
);
|
|
5889
5943
|
console.log(`summary: ${artifactSummary(artifact)}`);
|
|
5944
|
+
const entry = siteEntry(
|
|
5945
|
+
artifact.artifact_type,
|
|
5946
|
+
artifact.current_version.content_type,
|
|
5947
|
+
artifact.current_version.files ?? []
|
|
5948
|
+
);
|
|
5949
|
+
if (entry !== null) {
|
|
5950
|
+
console.log(
|
|
5951
|
+
`site: renders live from /${entry} \u2014 "tines issues artifacts site-link ${issue.project_name}/${issue.number} ${artifact.name}" for a viewable URL`
|
|
5952
|
+
);
|
|
5953
|
+
}
|
|
5890
5954
|
console.log("\nversions:");
|
|
5891
5955
|
table(
|
|
5892
5956
|
artifact.versions.map((v) => [
|
|
@@ -5936,10 +6000,13 @@ files (v${artifact.current_version.version}):`);
|
|
|
5936
6000
|
);
|
|
5937
6001
|
const withDescription = opts.description !== void 0 ? { description: opts.description } : {};
|
|
5938
6002
|
let artifact;
|
|
6003
|
+
let siteHtml = null;
|
|
5939
6004
|
if (plan.type === "folder") {
|
|
5940
6005
|
const dir = plan.source.dir;
|
|
5941
6006
|
const files = walkFolder(dir);
|
|
5942
6007
|
if (files.length === 0) die(`${dir} contains no files to snapshot`);
|
|
6008
|
+
const index = files.find((f) => f.path === ARTIFACT_SITE_INDEX);
|
|
6009
|
+
if (index) siteHtml = index.bytes.toString("utf8");
|
|
5943
6010
|
artifact = await api.uploadArtifactFolder(issue.id, name2, files);
|
|
5944
6011
|
if (opts.description !== void 0) {
|
|
5945
6012
|
artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
|
|
@@ -5957,15 +6024,18 @@ files (v${artifact.current_version.version}):`);
|
|
|
5957
6024
|
die(`cannot read ${fromPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5958
6025
|
}
|
|
5959
6026
|
}
|
|
6027
|
+
const contentType = plan.contentType ?? (fromPath === null ? "application/octet-stream" : sniffContentType(fromPath));
|
|
6028
|
+
if (siteEntry("file", contentType) !== null) siteHtml = bytes.toString("utf8");
|
|
5960
6029
|
artifact = await api.uploadArtifactFile(issue.id, name2, bytes, {
|
|
5961
6030
|
filename: plan.filename ?? (fromPath === null ? name2 : basename(fromPath)),
|
|
5962
|
-
contentType
|
|
6031
|
+
contentType
|
|
5963
6032
|
});
|
|
5964
6033
|
if (opts.description !== void 0) {
|
|
5965
6034
|
artifact = await api.putArtifact(issue.id, name2, { description: opts.description });
|
|
5966
6035
|
}
|
|
5967
6036
|
} else if (plan.type === "text") {
|
|
5968
6037
|
const content = readTextSource(plan.source);
|
|
6038
|
+
if (siteEntry("text", plan.contentType ?? null) !== null) siteHtml = content;
|
|
5969
6039
|
artifact = await api.putArtifact(issue.id, name2, {
|
|
5970
6040
|
type: "text",
|
|
5971
6041
|
content,
|
|
@@ -5996,6 +6066,12 @@ files (v${artifact.current_version.version}):`);
|
|
|
5996
6066
|
console.log(
|
|
5997
6067
|
`attached "${artifact.name}" v${artifact.current_version.version} (${artifactTypeLabel(artifact)}) to ${issue.project_name}/${issue.number} \u2014 fresh${gateNote}`
|
|
5998
6068
|
);
|
|
6069
|
+
if (siteHtml !== null) {
|
|
6070
|
+
console.log(
|
|
6071
|
+
`site: renders live \u2014 "tines issues artifacts site-link ${issue.project_name}/${issue.number} ${artifact.name}" for a viewable URL`
|
|
6072
|
+
);
|
|
6073
|
+
for (const warning of lintHtmlArtifact(siteHtml)) console.log(`warning: ${warning}`);
|
|
6074
|
+
}
|
|
5999
6075
|
}
|
|
6000
6076
|
);
|
|
6001
6077
|
withCommon(
|
|
@@ -6011,6 +6087,32 @@ files (v${artifact.current_version.version}):`);
|
|
|
6011
6087
|
`reaffirmed "${artifact.name}" on ${issue.project_name}/${issue.number}: v${artifact.current_version.version} reaffirms v${artifact.current_version.reaffirmed_from} \u2014 fresh as of now`
|
|
6012
6088
|
);
|
|
6013
6089
|
});
|
|
6090
|
+
withCommon(
|
|
6091
|
+
artifactsCmd.command("site-link <ref> <name>").description(
|
|
6092
|
+
"Mint a short-lived URL that renders an HTML artifact live (scripts running) \u2014 for screenshotting your own prototype"
|
|
6093
|
+
).option(
|
|
6094
|
+
"--version <n>",
|
|
6095
|
+
"pin the link to a specific version (defaults to current)",
|
|
6096
|
+
(v) => Number.parseInt(v, 10)
|
|
6097
|
+
)
|
|
6098
|
+
).action(async (ref, name2, opts) => {
|
|
6099
|
+
const api = client(opts);
|
|
6100
|
+
const issue = await resolveIssue(api, ref);
|
|
6101
|
+
const link = await api.createArtifactSiteLink(
|
|
6102
|
+
issue.id,
|
|
6103
|
+
name2,
|
|
6104
|
+
opts.version === void 0 ? {} : { version: opts.version }
|
|
6105
|
+
);
|
|
6106
|
+
if (opts.json) return printJson(link);
|
|
6107
|
+
console.log(link.url);
|
|
6108
|
+
const minutes = Math.max(1, Math.round((link.expires_at - Date.now()) / 6e4));
|
|
6109
|
+
console.log(`v${link.version}, expires in ~${minutes} minute${minutes === 1 ? "" : "s"}`);
|
|
6110
|
+
if (link.mode === "same-origin") {
|
|
6111
|
+
console.log(
|
|
6112
|
+
"mode: same-origin sandbox (opaque origin) \u2014 localStorage and cookies throw inside the page"
|
|
6113
|
+
);
|
|
6114
|
+
}
|
|
6115
|
+
});
|
|
6014
6116
|
withCommon(
|
|
6015
6117
|
artifactsCmd.command("get <ref> <name>").description("Fetch content (current version by default); a link/pr prints its URL").option(
|
|
6016
6118
|
"--version <n>",
|