spec-layer 0.2.1 → 0.3.0
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 +52 -10
- package/dist/cli.js +200 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,19 +11,23 @@ and writes it to disk.
|
|
|
11
11
|
## Quick start
|
|
12
12
|
|
|
13
13
|
After publishing a library from the plugin's Library screen, it shows a setup
|
|
14
|
-
command. Run it in your repository:
|
|
14
|
+
command. Run it once in your repository:
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
|
|
17
|
+
npx spec-layer setup --id lib_... --key sl_...
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
That
|
|
21
|
-
|
|
20
|
+
That records the library id, stores the key in a gitignored
|
|
21
|
+
`speclayer.local.json`, and writes `.speclayer/`. Every later command needs no
|
|
22
|
+
flags at all:
|
|
22
23
|
|
|
23
24
|
```bash
|
|
24
|
-
npx spec-layer
|
|
25
|
+
npx spec-layer pull
|
|
25
26
|
```
|
|
26
27
|
|
|
28
|
+
`init` still writes the config without a key or a network call, for a repo
|
|
29
|
+
that supplies the key from the environment instead.
|
|
30
|
+
|
|
27
31
|
## Installing, or not
|
|
28
32
|
|
|
29
33
|
`npx` needs no install step: it fetches the package and runs it. That is the
|
|
@@ -114,11 +118,49 @@ name, `show` refuses to guess and points you at `list`.
|
|
|
114
118
|
|
|
115
119
|
## The pull key
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
`spec-layer setup` stores the key in `speclayer.local.json` next to
|
|
122
|
+
`speclayer.json` and makes sure git ignores it before writing it. Every later
|
|
123
|
+
command in that directory needs no key. On POSIX systems the file is written
|
|
124
|
+
at mode `0600`; on Windows there is no equivalent permission bit, so it
|
|
125
|
+
inherits whatever the directory allows.
|
|
126
|
+
|
|
127
|
+
Commands that talk to the server resolve the key in this order:
|
|
128
|
+
|
|
129
|
+
1. `--key sl_...`
|
|
130
|
+
2. `SPEC_LAYER_KEY` in the environment
|
|
131
|
+
3. `speclayer.local.json`, when it was issued for the same library
|
|
132
|
+
|
|
133
|
+
Environment sits above the file so CI can supply a key without touching the
|
|
134
|
+
working tree. A stored key issued for a different library is ignored, and the
|
|
135
|
+
CLI says which library it belongs to rather than letting the server answer 401.
|
|
136
|
+
|
|
137
|
+
Treat the key as a secret: it grants read access to the published bundle.
|
|
138
|
+
`speclayer.local.json` is gitignored, never printed by any command, and never
|
|
139
|
+
copied into `speclayer.json`, `bundle.json`, `manifest.json`, or anything under
|
|
140
|
+
the output directory. If it leaks, rotate it from the plugin's Library screen,
|
|
141
|
+
then run the new setup command. The old key stops working once the change
|
|
142
|
+
propagates, which can take up to about a minute.
|
|
143
|
+
|
|
144
|
+
Re-running `setup` replaces the stored key and keeps the rest of your setup:
|
|
145
|
+
with no `--out` and no selection flag it preserves the output directory and the
|
|
146
|
+
`include` block already in `speclayer.json` rather than resetting them to the
|
|
147
|
+
defaults. Pass `--out` or a selection flag to change them. (`init` still
|
|
148
|
+
overwrites `speclayer.json` outright, which is what a first run is for.)
|
|
149
|
+
|
|
150
|
+
Outside a git working tree, the key is still stored and the CLI says it left
|
|
151
|
+
`.gitignore` alone. Inside one, `setup` refuses to write the key whenever it
|
|
152
|
+
cannot confirm the file will be ignored, and says what to do instead. Three
|
|
153
|
+
cases refuse:
|
|
154
|
+
|
|
155
|
+
- `.gitignore` cannot be written.
|
|
156
|
+
- git itself could not be run, anywhere inside a working tree.
|
|
157
|
+
- the entry is in `.gitignore`, but git still does not ignore the file. That
|
|
158
|
+
almost always means `speclayer.local.json` is already tracked, and the CLI
|
|
159
|
+
names `git rm --cached speclayer.local.json` as the way out.
|
|
160
|
+
|
|
161
|
+
git decides in every case. The entry sitting in `.gitignore` is not taken as
|
|
162
|
+
proof, because `git check-ignore` does not report a tracked file as ignored no
|
|
163
|
+
matter what the ignore rules say.
|
|
122
164
|
|
|
123
165
|
## What `pull` writes
|
|
124
166
|
|
package/dist/cli.js
CHANGED
|
@@ -559,7 +559,7 @@ var require_sha256 = __commonJS({
|
|
|
559
559
|
import { parseArgs } from "node:util";
|
|
560
560
|
|
|
561
561
|
// src/commands.ts
|
|
562
|
-
import { join as
|
|
562
|
+
import { join as join5 } from "node:path";
|
|
563
563
|
|
|
564
564
|
// ../extractor/src/statesMatrix.ts
|
|
565
565
|
var STATE_ORDER = [
|
|
@@ -824,8 +824,41 @@ function parseBundle(raw) {
|
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
// src/config.ts
|
|
827
|
-
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
827
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "node:fs";
|
|
828
|
+
import { join as join2 } from "node:path";
|
|
829
|
+
|
|
830
|
+
// src/credentials.ts
|
|
831
|
+
import { readFileSync, writeFileSync, existsSync, chmodSync } from "node:fs";
|
|
828
832
|
import { join } from "node:path";
|
|
833
|
+
var CREDENTIALS_NAME = "speclayer.local.json";
|
|
834
|
+
var unreadable = () => new Error(
|
|
835
|
+
`${CREDENTIALS_NAME} cannot be read. Delete it, then run the setup command from the plugin's Library screen.`
|
|
836
|
+
);
|
|
837
|
+
function readCredentials(cwd) {
|
|
838
|
+
const path = join(cwd, CREDENTIALS_NAME);
|
|
839
|
+
if (!existsSync(path)) return null;
|
|
840
|
+
let parsed;
|
|
841
|
+
try {
|
|
842
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
843
|
+
} catch {
|
|
844
|
+
throw unreadable();
|
|
845
|
+
}
|
|
846
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw unreadable();
|
|
847
|
+
const record = parsed;
|
|
848
|
+
if (typeof record.libraryId !== "string" || typeof record.key !== "string") throw unreadable();
|
|
849
|
+
return { libraryId: record.libraryId, key: record.key };
|
|
850
|
+
}
|
|
851
|
+
function writeCredentials(cwd, stored) {
|
|
852
|
+
const path = join(cwd, CREDENTIALS_NAME);
|
|
853
|
+
const replaced = existsSync(path);
|
|
854
|
+
const body = { libraryId: stored.libraryId, key: stored.key };
|
|
855
|
+
writeFileSync(path, `${JSON.stringify(body, null, 2)}
|
|
856
|
+
`, { mode: 384 });
|
|
857
|
+
chmodSync(path, 384);
|
|
858
|
+
return { replaced };
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// src/config.ts
|
|
829
862
|
var DEFAULT_API = "https://api.spec-layer.com";
|
|
830
863
|
var DEFAULT_OUT_DIR = ".speclayer";
|
|
831
864
|
var CONFIG_NAME = "speclayer.json";
|
|
@@ -843,11 +876,11 @@ function parseInclude(value) {
|
|
|
843
876
|
};
|
|
844
877
|
}
|
|
845
878
|
function readConfig(cwd) {
|
|
846
|
-
const path =
|
|
847
|
-
if (!
|
|
879
|
+
const path = join2(cwd, CONFIG_NAME);
|
|
880
|
+
if (!existsSync2(path)) return null;
|
|
848
881
|
let parsed;
|
|
849
882
|
try {
|
|
850
|
-
parsed = JSON.parse(
|
|
883
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
851
884
|
} catch {
|
|
852
885
|
throw invalidConfig();
|
|
853
886
|
}
|
|
@@ -865,20 +898,31 @@ function writeConfig(cwd, config) {
|
|
|
865
898
|
outDir: config.outDir,
|
|
866
899
|
...config.include ? { include: config.include } : {}
|
|
867
900
|
};
|
|
868
|
-
|
|
901
|
+
writeFileSync2(join2(cwd, CONFIG_NAME), `${JSON.stringify(body, null, 2)}
|
|
869
902
|
`);
|
|
870
903
|
}
|
|
871
904
|
function resolveOptions(cwd, flags, env, manifestLibraryId) {
|
|
872
905
|
const config = readConfig(cwd);
|
|
873
906
|
const outDir = flags.out ?? config?.outDir ?? DEFAULT_OUT_DIR;
|
|
874
|
-
const libraryId = flags.id ?? config?.libraryId ?? manifestLibraryId(
|
|
907
|
+
const libraryId = flags.id ?? config?.libraryId ?? manifestLibraryId(join2(cwd, outDir));
|
|
908
|
+
const supplied = flags.key || env.SPEC_LAYER_KEY || null;
|
|
909
|
+
let storedKey = null;
|
|
910
|
+
let storedKeyFor;
|
|
911
|
+
if (!supplied) {
|
|
912
|
+
const stored = readCredentials(cwd);
|
|
913
|
+
if (stored) {
|
|
914
|
+
if (libraryId && stored.libraryId === libraryId) storedKey = stored.key || null;
|
|
915
|
+
else storedKeyFor = stored.libraryId;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
875
918
|
return {
|
|
876
919
|
libraryId,
|
|
877
920
|
outDir,
|
|
878
921
|
// A trailing slash would build "//v1/..." paths the proxy router 404s on.
|
|
879
922
|
api: (flags.api ?? env.SPEC_LAYER_API ?? DEFAULT_API).replace(/\/+$/, ""),
|
|
880
|
-
key:
|
|
881
|
-
...config?.include ? { include: config.include } : {}
|
|
923
|
+
key: supplied ?? storedKey,
|
|
924
|
+
...config?.include ? { include: config.include } : {},
|
|
925
|
+
...storedKeyFor ? { storedKeyFor } : {}
|
|
882
926
|
};
|
|
883
927
|
}
|
|
884
928
|
|
|
@@ -899,7 +943,10 @@ async function fetchBundle(opts) {
|
|
|
899
943
|
}
|
|
900
944
|
if (res.status === 304) return { kind: "not_modified" };
|
|
901
945
|
if (res.status === 401) {
|
|
902
|
-
return {
|
|
946
|
+
return {
|
|
947
|
+
kind: "error",
|
|
948
|
+
message: "Key was rotated or revoked. Run the setup command from the plugin's Library screen to store the current key."
|
|
949
|
+
};
|
|
903
950
|
}
|
|
904
951
|
if (res.status === 404) return { kind: "error", message: "Library not found. It may have been unpublished." };
|
|
905
952
|
if (!res.ok) return { kind: "error", message: `Request failed with HTTP ${res.status}.` };
|
|
@@ -913,8 +960,8 @@ async function fetchBundle(opts) {
|
|
|
913
960
|
}
|
|
914
961
|
|
|
915
962
|
// src/files.ts
|
|
916
|
-
import { mkdirSync, writeFileSync as
|
|
917
|
-
import { join as
|
|
963
|
+
import { mkdirSync, writeFileSync as writeFileSync3, readFileSync as readFileSync3, readdirSync, rmSync, renameSync, existsSync as existsSync3 } from "node:fs";
|
|
964
|
+
import { join as join3, dirname, relative, resolve, isAbsolute } from "node:path";
|
|
918
965
|
|
|
919
966
|
// src/selection.ts
|
|
920
967
|
var DEFAULT_SELECTION = { foundation: true, components: null };
|
|
@@ -953,19 +1000,19 @@ function slugify(name) {
|
|
|
953
1000
|
return slug || "component";
|
|
954
1001
|
}
|
|
955
1002
|
function readManifest(outDir) {
|
|
956
|
-
const path =
|
|
957
|
-
if (!
|
|
1003
|
+
const path = join3(outDir, "manifest.json");
|
|
1004
|
+
if (!existsSync3(path)) return null;
|
|
958
1005
|
try {
|
|
959
|
-
return JSON.parse(
|
|
1006
|
+
return JSON.parse(readFileSync3(path, "utf8"));
|
|
960
1007
|
} catch {
|
|
961
1008
|
return null;
|
|
962
1009
|
}
|
|
963
1010
|
}
|
|
964
1011
|
function readLocalBundle(outDir) {
|
|
965
|
-
const path =
|
|
966
|
-
if (!
|
|
1012
|
+
const path = join3(outDir, "bundle.json");
|
|
1013
|
+
if (!existsSync3(path)) return null;
|
|
967
1014
|
try {
|
|
968
|
-
return parseBundle(
|
|
1015
|
+
return parseBundle(readFileSync3(path, "utf8"));
|
|
969
1016
|
} catch {
|
|
970
1017
|
throw new Error(`${path} could not be read as a library bundle. Run spec-layer pull again.`);
|
|
971
1018
|
}
|
|
@@ -996,7 +1043,7 @@ function assertReplaceable(outDir, cwd) {
|
|
|
996
1043
|
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
997
1044
|
throw new Error('The output directory must sit inside the current directory, not be "." or a parent of it.');
|
|
998
1045
|
}
|
|
999
|
-
if (
|
|
1046
|
+
if (existsSync3(outDir) && !existsSync3(join3(outDir, "manifest.json")) && readdirSync(outDir).length > 0) {
|
|
1000
1047
|
throw new Error(`${outDir} exists and was not written by spec-layer pull. Choose an empty or new directory.`);
|
|
1001
1048
|
}
|
|
1002
1049
|
}
|
|
@@ -1009,9 +1056,9 @@ function writeBundleFiles(opts) {
|
|
|
1009
1056
|
rmSync(staging, { recursive: true, force: true });
|
|
1010
1057
|
const written = [];
|
|
1011
1058
|
const put = (rel, content) => {
|
|
1012
|
-
const path =
|
|
1059
|
+
const path = join3(staging, rel);
|
|
1013
1060
|
mkdirSync(dirname(path), { recursive: true });
|
|
1014
|
-
|
|
1061
|
+
writeFileSync3(path, content);
|
|
1015
1062
|
written.push(rel);
|
|
1016
1063
|
};
|
|
1017
1064
|
try {
|
|
@@ -1057,6 +1104,60 @@ function writeBundleFiles(opts) {
|
|
|
1057
1104
|
return written;
|
|
1058
1105
|
}
|
|
1059
1106
|
|
|
1107
|
+
// src/gitignore.ts
|
|
1108
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
1109
|
+
import { spawnSync } from "node:child_process";
|
|
1110
|
+
import { join as join4, dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
1111
|
+
var COMMENT = "# Spec Layer pull key, not for committing";
|
|
1112
|
+
function git(cwd, args) {
|
|
1113
|
+
const res = spawnSync("git", args, { cwd, stdio: ["ignore", "pipe", "ignore"], encoding: "utf8" });
|
|
1114
|
+
if (res.error || res.status === null) return { ranGit: false };
|
|
1115
|
+
return { ranGit: true, status: res.status, stdout: res.stdout ?? "" };
|
|
1116
|
+
}
|
|
1117
|
+
function insideWorkTreeWithoutGit(cwd) {
|
|
1118
|
+
let dir = resolve2(cwd);
|
|
1119
|
+
for (; ; ) {
|
|
1120
|
+
if (existsSync4(join4(dir, ".git"))) return true;
|
|
1121
|
+
const parent = dirname2(dir);
|
|
1122
|
+
if (parent === dir) return false;
|
|
1123
|
+
dir = parent;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function hasEntryLine(body, fileName) {
|
|
1127
|
+
return body.split("\n").some((line) => line.trim() === fileName);
|
|
1128
|
+
}
|
|
1129
|
+
function ensureIgnored(cwd, fileName) {
|
|
1130
|
+
const inWorkTree = git(cwd, ["rev-parse", "--is-inside-work-tree"]);
|
|
1131
|
+
if (!inWorkTree.ranGit) {
|
|
1132
|
+
return insideWorkTreeWithoutGit(cwd) ? { kind: "no-git", line: fileName } : { kind: "not-a-repo" };
|
|
1133
|
+
}
|
|
1134
|
+
if (inWorkTree.status !== 0 || inWorkTree.stdout.trim() !== "true") return { kind: "not-a-repo" };
|
|
1135
|
+
const checkIgnore = git(cwd, ["check-ignore", "-q", fileName]);
|
|
1136
|
+
if (checkIgnore.ranGit && checkIgnore.status === 0) return { kind: "already" };
|
|
1137
|
+
const path = join4(cwd, ".gitignore");
|
|
1138
|
+
const existed = existsSync4(path);
|
|
1139
|
+
try {
|
|
1140
|
+
if (!existed) {
|
|
1141
|
+
writeFileSync4(path, `${COMMENT}
|
|
1142
|
+
${fileName}
|
|
1143
|
+
`);
|
|
1144
|
+
} else {
|
|
1145
|
+
const body = readFileSync4(path, "utf8");
|
|
1146
|
+
if (!hasEntryLine(body, fileName)) {
|
|
1147
|
+
const lead = body.length === 0 || body.endsWith("\n") ? "" : "\n";
|
|
1148
|
+
writeFileSync4(path, `${body}${lead}${COMMENT}
|
|
1149
|
+
${fileName}
|
|
1150
|
+
`);
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
} catch {
|
|
1154
|
+
return { kind: "refused", line: fileName };
|
|
1155
|
+
}
|
|
1156
|
+
const recheck = git(cwd, ["check-ignore", "-q", fileName]);
|
|
1157
|
+
if (!recheck.ranGit || recheck.status !== 0) return { kind: "still-not-ignored", line: fileName };
|
|
1158
|
+
return existed ? { kind: "added" } : { kind: "created" };
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1060
1161
|
// src/commands.ts
|
|
1061
1162
|
var NO_LOCAL_PULL = "No local pull found. Run spec-layer pull.";
|
|
1062
1163
|
function manifestReader() {
|
|
@@ -1086,7 +1187,7 @@ function runInit(cwd, flags, io2) {
|
|
|
1086
1187
|
const outDir = flags.out ?? DEFAULT_OUT_DIR;
|
|
1087
1188
|
writeConfig(cwd, { libraryId: flags.id, outDir, ...include ? { include } : {} });
|
|
1088
1189
|
io2.out(`Wrote speclayer.json (library ${flags.id}, output ${outDir}).`);
|
|
1089
|
-
io2.out(
|
|
1190
|
+
io2.out(`The pull key is not stored here. Run spec-layer setup to store it in ${CREDENTIALS_NAME}, or set SPEC_LAYER_KEY.`);
|
|
1090
1191
|
return 0;
|
|
1091
1192
|
}
|
|
1092
1193
|
function resolved(cwd, flags, env, io2, manifestAt) {
|
|
@@ -1098,18 +1199,18 @@ function resolved(cwd, flags, env, io2, manifestAt) {
|
|
|
1098
1199
|
return null;
|
|
1099
1200
|
}
|
|
1100
1201
|
if (!opts.libraryId) {
|
|
1101
|
-
io2.err("No library id. Pass --id lib_..., or run spec-layer init first.");
|
|
1202
|
+
io2.err(opts.storedKeyFor ? `No library id. ${CREDENTIALS_NAME} holds a key for library ${opts.storedKeyFor}. Pass --id ${opts.storedKeyFor}, or run spec-layer init first.` : "No library id. Pass --id lib_..., or run spec-layer init first.");
|
|
1102
1203
|
return null;
|
|
1103
1204
|
}
|
|
1104
1205
|
if (!opts.key) {
|
|
1105
|
-
io2.err("No pull key.
|
|
1206
|
+
io2.err(opts.storedKeyFor ? `The key in ${CREDENTIALS_NAME} was issued for library ${opts.storedKeyFor}, not ${opts.libraryId}. Run the setup command from the plugin's Library screen.` : "No pull key. Run the setup command from the plugin's Library screen, or set SPEC_LAYER_KEY.");
|
|
1106
1207
|
return null;
|
|
1107
1208
|
}
|
|
1108
1209
|
return opts;
|
|
1109
1210
|
}
|
|
1110
1211
|
function resolvedOutDir(cwd, flags, io2) {
|
|
1111
1212
|
try {
|
|
1112
|
-
return
|
|
1213
|
+
return join5(cwd, flags.out ?? readConfig(cwd)?.outDir ?? DEFAULT_OUT_DIR);
|
|
1113
1214
|
} catch (err) {
|
|
1114
1215
|
io2.err(errorText(err));
|
|
1115
1216
|
return null;
|
|
@@ -1122,6 +1223,72 @@ function describePull(bundle, selection, selected) {
|
|
|
1122
1223
|
if (!bundle.foundation) return components;
|
|
1123
1224
|
return selection.foundation ? `foundation + ${components}` : `${components}, no foundation`;
|
|
1124
1225
|
}
|
|
1226
|
+
async function runSetup(cwd, flags, env, io2, fetcher) {
|
|
1227
|
+
if (!flags.id) {
|
|
1228
|
+
io2.err("spec-layer setup needs --id lib_... (shown in the plugin after publishing).");
|
|
1229
|
+
return 1;
|
|
1230
|
+
}
|
|
1231
|
+
const key = flags.key ?? env.SPEC_LAYER_KEY;
|
|
1232
|
+
if (!key) {
|
|
1233
|
+
io2.err("spec-layer setup needs --key sl_..., or SPEC_LAYER_KEY in the environment.");
|
|
1234
|
+
return 1;
|
|
1235
|
+
}
|
|
1236
|
+
let include;
|
|
1237
|
+
try {
|
|
1238
|
+
include = selectionFromFlags(flags);
|
|
1239
|
+
} catch (err) {
|
|
1240
|
+
io2.err(errorText(err));
|
|
1241
|
+
return 1;
|
|
1242
|
+
}
|
|
1243
|
+
let existing = null;
|
|
1244
|
+
try {
|
|
1245
|
+
existing = readConfig(cwd);
|
|
1246
|
+
} catch {
|
|
1247
|
+
existing = null;
|
|
1248
|
+
}
|
|
1249
|
+
const outDir = flags.out ?? existing?.outDir ?? DEFAULT_OUT_DIR;
|
|
1250
|
+
const keptInclude = include ?? existing?.include ?? null;
|
|
1251
|
+
writeConfig(cwd, { libraryId: flags.id, outDir, ...keptInclude ? { include: keptInclude } : {} });
|
|
1252
|
+
io2.out(`Wrote speclayer.json (library ${flags.id}, output ${outDir}).`);
|
|
1253
|
+
const ignored = ensureIgnored(cwd, CREDENTIALS_NAME);
|
|
1254
|
+
switch (ignored.kind) {
|
|
1255
|
+
case "refused":
|
|
1256
|
+
io2.err(`Could not add ${CREDENTIALS_NAME} to .gitignore, so the key was not written.`);
|
|
1257
|
+
io2.err(`Add this line to .gitignore, then run the command again:
|
|
1258
|
+
${ignored.line}`);
|
|
1259
|
+
return 1;
|
|
1260
|
+
case "no-git":
|
|
1261
|
+
io2.err(`Could not run git, so it could not confirm ${CREDENTIALS_NAME} would be ignored. The key was not written.`);
|
|
1262
|
+
io2.err(`Add this line to .gitignore, then run the command again:
|
|
1263
|
+
${ignored.line}`);
|
|
1264
|
+
return 1;
|
|
1265
|
+
case "still-not-ignored":
|
|
1266
|
+
io2.err(`${ignored.line} is listed in .gitignore, but git still does not ignore it, so the key was not written.`);
|
|
1267
|
+
io2.err(`The most likely reason is that ${ignored.line} is already tracked. Run this, then run the command again:
|
|
1268
|
+
git rm --cached ${ignored.line}`);
|
|
1269
|
+
return 1;
|
|
1270
|
+
case "created":
|
|
1271
|
+
io2.out(`Created .gitignore with ${CREDENTIALS_NAME}.`);
|
|
1272
|
+
break;
|
|
1273
|
+
case "added":
|
|
1274
|
+
io2.out(`Added ${CREDENTIALS_NAME} to .gitignore.`);
|
|
1275
|
+
break;
|
|
1276
|
+
case "already":
|
|
1277
|
+
io2.out(`${CREDENTIALS_NAME} is already ignored by git.`);
|
|
1278
|
+
break;
|
|
1279
|
+
case "not-a-repo":
|
|
1280
|
+
io2.out("Not a git repository, so .gitignore was left alone.");
|
|
1281
|
+
break;
|
|
1282
|
+
default: {
|
|
1283
|
+
const exhaustive = ignored;
|
|
1284
|
+
void exhaustive;
|
|
1285
|
+
return 1;
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
const { replaced } = writeCredentials(cwd, { libraryId: flags.id, key });
|
|
1289
|
+
io2.out(replaced ? `Replaced the stored key in ${CREDENTIALS_NAME}.` : `Stored the pull key in ${CREDENTIALS_NAME}.`);
|
|
1290
|
+
return runPull(cwd, { ...flags, key }, env, io2, fetcher);
|
|
1291
|
+
}
|
|
1125
1292
|
async function runPull(cwd, flags, env, io2, fetcher) {
|
|
1126
1293
|
const manifestAt = manifestReader();
|
|
1127
1294
|
const opts = resolved(cwd, flags, env, io2, manifestAt);
|
|
@@ -1133,7 +1300,7 @@ async function runPull(cwd, flags, env, io2, fetcher) {
|
|
|
1133
1300
|
io2.err(errorText(err));
|
|
1134
1301
|
return 1;
|
|
1135
1302
|
}
|
|
1136
|
-
const manifest = manifestAt(
|
|
1303
|
+
const manifest = manifestAt(join5(cwd, opts.outDir));
|
|
1137
1304
|
const etag = manifest && sameSelection(manifest.selection ?? DEFAULT_SELECTION, selection) ? manifest.bundleHash : void 0;
|
|
1138
1305
|
const result = await fetchBundle({
|
|
1139
1306
|
api: opts.api,
|
|
@@ -1155,7 +1322,7 @@ async function runPull(cwd, flags, env, io2, fetcher) {
|
|
|
1155
1322
|
const bundle = parseBundle(result.raw);
|
|
1156
1323
|
const selected = selectComponents(bundle, selection);
|
|
1157
1324
|
written = writeBundleFiles({
|
|
1158
|
-
outDir:
|
|
1325
|
+
outDir: join5(cwd, opts.outDir),
|
|
1159
1326
|
cwd,
|
|
1160
1327
|
raw: result.raw,
|
|
1161
1328
|
bundle,
|
|
@@ -1178,7 +1345,7 @@ async function runStatus(cwd, flags, env, io2, fetcher) {
|
|
|
1178
1345
|
const manifestAt = manifestReader();
|
|
1179
1346
|
const opts = resolved(cwd, flags, env, io2, manifestAt);
|
|
1180
1347
|
if (!opts) return 1;
|
|
1181
|
-
const manifest = manifestAt(
|
|
1348
|
+
const manifest = manifestAt(join5(cwd, opts.outDir));
|
|
1182
1349
|
if (!manifest) {
|
|
1183
1350
|
io2.err(NO_LOCAL_PULL);
|
|
1184
1351
|
return 2;
|
|
@@ -1269,6 +1436,8 @@ Available: ${available || "none"}.`);
|
|
|
1269
1436
|
var USAGE = `spec-layer <command>
|
|
1270
1437
|
|
|
1271
1438
|
Commands:
|
|
1439
|
+
setup --id lib_... --key sl_... [--out DIR] [selection]
|
|
1440
|
+
store the key, then pull
|
|
1272
1441
|
init --id lib_... [--out DIR] [selection] write speclayer.json
|
|
1273
1442
|
pull [--id lib_...] [--key sl_...] [selection]
|
|
1274
1443
|
fetch the library into DIR (default .speclayer)
|
|
@@ -1277,13 +1446,13 @@ Commands:
|
|
|
1277
1446
|
show foundation | component NAME [--canonical]
|
|
1278
1447
|
print one artifact's AI YAML (or canonical JSON)
|
|
1279
1448
|
|
|
1280
|
-
Selection (pull and init; flags replace the include block in speclayer.json):
|
|
1449
|
+
Selection (setup, pull and init; flags replace the include block in speclayer.json):
|
|
1281
1450
|
--only foundation | components write just the foundation, or just components
|
|
1282
1451
|
--component NAME write only this component (repeatable, matched by slug)
|
|
1283
1452
|
|
|
1284
1453
|
Options:
|
|
1285
1454
|
--api URL override the API origin (default https://api.spec-layer.com)
|
|
1286
|
-
The pull key comes from --key or
|
|
1455
|
+
The pull key comes from --key, SPEC_LAYER_KEY, or speclayer.local.json written by setup.`;
|
|
1287
1456
|
var io = {
|
|
1288
1457
|
out: (l) => console.log(l),
|
|
1289
1458
|
err: (l) => console.error(l),
|
|
@@ -1314,6 +1483,7 @@ async function main() {
|
|
|
1314
1483
|
const command = positionals[0];
|
|
1315
1484
|
const cwd = process.cwd();
|
|
1316
1485
|
try {
|
|
1486
|
+
if (command === "setup") return await runSetup(cwd, values, process.env, io);
|
|
1317
1487
|
if (command === "init") return runInit(cwd, values, io);
|
|
1318
1488
|
if (command === "pull") return await runPull(cwd, values, process.env, io);
|
|
1319
1489
|
if (command === "status") return await runStatus(cwd, values, process.env, io);
|