Skip to content
This repository was archived by the owner on Dec 8, 2025. It is now read-only.

Commit 81dee14

Browse files
authored
Merge pull request #23 from microsoft/initcli
feat: support for init command
2 parents 8b1bce2 + 2b2ae9b commit 81dee14

15 files changed

Lines changed: 339 additions & 41 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ runtime/devicescript-vm/dist/wasmpre.d.ts.map
1010
compiler/src/prelude.ts
1111
website/solutions
1212
website/static/dist/devicescript-compiler.js
13+
temp/
14+
jacs/lib/devicescript-spec.d.ts

cli/src/build.ts

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { join } from "node:path"
2-
import { watch } from "node:fs"
2+
import { existsSync, watch } from "node:fs"
33
import {
44
readFileSync,
55
writeFileSync,
@@ -12,8 +12,9 @@ import {
1212
jacdacDefaultSpecifications,
1313
JacsDiagnostic,
1414
DEVS_BYTECODE_FILE,
15+
formatDiagnostics,
1516
} from "devicescript-compiler"
16-
import { CmdOptions } from "./command"
17+
import { BINDIR, CmdOptions, debug, error, log } from "./command"
1718
import { devtools } from "./devtools"
1819

1920
function jacsFactory() {
@@ -23,7 +24,7 @@ function jacsFactory() {
2324
// @ts-ignore
2425
global.Blob = require("buffer").Blob
2526
} catch {
26-
console.log("can't load websocket-polyfill")
27+
log("can't load websocket-polyfill")
2728
}
2829
return d()
2930
}
@@ -38,7 +39,7 @@ async function getHost(options: BuildOptions & CmdOptions) {
3839
const jacsHost = {
3940
write: (fn: string, cont: string) => {
4041
const p = join(outdir, fn)
41-
if (options.verbose) console.debug(`write ${p}`)
42+
if (options.verbose) debug(`write ${p}`)
4243
writeFileSync(p, cont)
4344
if (
4445
fn.endsWith(".jasm") &&
@@ -48,10 +49,10 @@ async function getHost(options: BuildOptions & CmdOptions) {
4849
throw new Error("bad disassembly")
4950
},
5051
log: (msg: string) => {
51-
if (options.verbose) console.log(msg)
52+
if (options.verbose) log(msg)
5253
},
5354
error: (err: JacsDiagnostic) => {
54-
console.error(err)
55+
error(formatDiagnostics([err]))
5556
},
5657
mainFileName: () => options.mainFileName || "",
5758
getSpecs: () => jacdacDefaultSpecifications,
@@ -87,9 +88,16 @@ export interface BuildOptions {
8788
export async function build(file: string, options: BuildOptions & CmdOptions) {
8889
file = file || "main.ts"
8990
options = options || {}
90-
options.outDir = options.outDir || "./built"
91+
options.outDir = options.outDir || BINDIR
9192
options.mainFileName = file
9293

94+
if (!existsSync(file)) {
95+
// otherwise we throw
96+
error(`${file} does not exist`)
97+
return
98+
}
99+
100+
ensureDirSync(options.outDir)
93101
await buildOnce(file, options)
94102
if (options.watch) await buildWatch(file, options)
95103
}
@@ -98,10 +106,10 @@ async function buildWatch(file: string, options: BuildOptions) {
98106
const bytecodeFile = join(options.outDir, DEVS_BYTECODE_FILE)
99107

100108
// start watch source file
101-
console.log(`watching ${file}...`)
109+
log(`watching ${file}...`)
102110
const work = debounce(
103111
async () => {
104-
console.debug(`change detected...`)
112+
debug(`change detected...`)
105113
await buildOnce(file, options)
106114
},
107115
500,
@@ -121,8 +129,8 @@ async function buildOnce(file: string, options: BuildOptions & CmdOptions) {
121129
await compileBuf(buf, { ...options, mainFileName: file })
122130
} catch (e) {
123131
if (options.verbose) {
124-
console.debug(e.message)
125-
console.debug(e.stack)
132+
debug(e.message)
133+
debug(e.stack)
126134
}
127135
throw e
128136
}

cli/src/cli.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { program } from "commander"
22
import pkg from "../package.json"
33
import { build } from "./build"
44
import { devtools } from "./devtools"
5+
import init from "./init"
56

67
export async function mainCli() {
78
program
@@ -27,6 +28,13 @@ export async function mainCli() {
2728
.arguments("[file.ts]")
2829
.action(build)
2930

31+
program
32+
.command("init")
33+
.description("configures the current directory for devicescript")
34+
.option("-f, --force", "force overwrite existing files")
35+
.option("--spaces <number>", "number of spaces when generating JSON")
36+
.action(init)
37+
3038
program
3139
.command("devtools")
3240
.description("launches a local deveplopement tools server")

cli/src/command.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,9 @@ export interface CmdOptions {
22
verbose?: boolean
33
noVerify?: boolean
44
}
5+
export const GENDIR = ".devicescript"
6+
export const LIBDIR = `${GENDIR}/lib`
7+
export const BINDIR = `${GENDIR}/bin`
8+
export const log = console.log
9+
export const debug = console.debug
10+
export const error = console.error

cli/src/devtools.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,8 @@ import https from "https"
66
import url from "url"
77
import net from "net"
88
import fs from "fs"
9-
import { CmdOptions } from "./command"
9+
import { CmdOptions, debug, error, log } from "./command"
1010

11-
const log = console.log
12-
const debug = console.debug
13-
const error = console.error
1411
const dasboardPath = "editors/devicescript"
1512

1613
function fetchProxy(localhost: boolean): Promise<string> {

cli/src/init.ts

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import { CmdOptions, debug, GENDIR, LIBDIR, log } from "./command"
2+
import { basename, join } from "node:path"
3+
import { cwd } from "node:process"
4+
import {
5+
pathExistsSync,
6+
writeFileSync,
7+
writeJSONSync,
8+
readJSONSync,
9+
emptyDirSync,
10+
readFileSync,
11+
} from "fs-extra"
12+
import { preludeFiles } from "devicescript-compiler"
13+
14+
const TSCONFIG = "tsconfig.json"
15+
const MAIN = "main.ts"
16+
const GITIGNORE = ".gitignore"
17+
const PKG = "package.json"
18+
19+
const tsConfig: any = {
20+
compilerOptions: {
21+
moduleResolution: "node",
22+
target: "es2022",
23+
module: "es2015",
24+
lib: [],
25+
strict: true,
26+
strictNullChecks: false,
27+
strictFunctionTypes: true,
28+
sourceMap: false,
29+
declaration: false,
30+
experimentalDecorators: true,
31+
preserveConstEnums: true,
32+
noImplicitThis: true,
33+
isolatedModules: true,
34+
noImplicitAny: true,
35+
types: [],
36+
},
37+
include: ["*.ts", `${LIBDIR}/*.ts`],
38+
}
39+
40+
export interface InitOptions {
41+
force?: boolean
42+
spaces?: number
43+
}
44+
45+
export default function init(options: InitOptions & CmdOptions) {
46+
const { force, spaces = 4 } = options
47+
log(`Initializing files for DeviceScript project`)
48+
// tsconfig.json
49+
if (!pathExistsSync(TSCONFIG) || force) {
50+
debug(`write ${TSCONFIG}`)
51+
writeJSONSync(TSCONFIG, tsConfig, { spaces })
52+
} else {
53+
debug(`skip ${TSCONFIG}, already exists`)
54+
}
55+
56+
// typescript definitions
57+
emptyDirSync(LIBDIR)
58+
debug(`write ${LIBDIR}/*`)
59+
const prelude = preludeFiles()
60+
for (const fn of Object.keys(prelude)) {
61+
writeFileSync(join(LIBDIR, fn), prelude[fn])
62+
}
63+
64+
// .gitignore
65+
const gid = `${GENDIR}/\n`
66+
if (!pathExistsSync(GITIGNORE)) {
67+
debug(`write ${GITIGNORE}`)
68+
writeFileSync(GITIGNORE, gid, { encoding: "utf8" })
69+
} else {
70+
const gitignore = readFileSync(GITIGNORE, { encoding: "utf8" })
71+
if (gitignore.indexOf(gid) < 0) {
72+
debug(`update ${GITIGNORE}`)
73+
writeFileSync(GITIGNORE, `${gitignore}\n${gid}`, {
74+
encoding: "utf8",
75+
})
76+
}
77+
}
78+
79+
// main.ts
80+
if (!pathExistsSync(MAIN)) {
81+
debug(`write ${MAIN}`)
82+
writeFileSync(
83+
MAIN,
84+
`// keep this line to force module mode
85+
export {}
86+
87+
`,
88+
{ encoding: "utf8" }
89+
)
90+
}
91+
92+
// package.json
93+
let pkgChanged = false
94+
const pkg = pathExistsSync(PKG)
95+
? readJSONSync(PKG)
96+
: {
97+
name: basename(cwd()),
98+
}
99+
if (!pkg.devicescript) {
100+
pkgChanged = true
101+
pkg.devicescript = {}
102+
}
103+
if (!pkg.scripts?.["init"]) {
104+
pkgChanged = true
105+
pkg.scripts = pkg.scripts || {}
106+
pkg.scripts["init"] = `devsc init`
107+
}
108+
if (!pkg.scripts?.["build"]) {
109+
pkgChanged = true
110+
pkg.scripts = pkg.scripts || {}
111+
pkg.scripts["build"] = `devsc build`
112+
}
113+
if (!pkg.scripts?.["start"]) {
114+
pkgChanged = true
115+
pkg.scripts = pkg.scripts || {}
116+
pkg.scripts["start"] = `devsc build --watch`
117+
}
118+
if (pkgChanged) {
119+
debug(`write ${PKG}`)
120+
writeJSONSync(PKG, pkg, { spaces })
121+
}
122+
123+
// help message
124+
log(`Your DeviceScript project is ready`)
125+
log(`to start the local development, run "yarn start"`)
126+
log(`to build binaries, run "yarn build"`)
127+
}

compiler/build.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ function distCopy(from, to) {
3636
console.debug(`cp ${from} ${to}`)
3737
try {
3838
fs.mkdirSync(path.dirname(to))
39-
} catch {}
39+
} catch { }
4040
fs.copyFileSync(from, to)
4141
fs.utimesSync(to, new Date(), new Date(fromT))
4242
}
@@ -96,18 +96,17 @@ const files = {
9696
"../cli/built/devicescript-cli.cjs": "../cli/src/cli.ts",
9797
}
9898

99+
const specname = "devicescript-spec.d.ts"
99100
function buildPrelude(folder, outp) {
100101
const files = fs.readdirSync(folder)
101102
files.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0))
102103

103104
const filecont = {}
104105
for (const fn of files) {
106+
if (fn == specname) continue
105107
filecont[fn] = fs.readFileSync(folder + "/" + fn, "utf-8")
106108
}
107109

108-
const specs = "../runtime/jacdac-c/jacdac/dist/devicescript-spec.d.ts"
109-
filecont["../" + specs] = fs.readFileSync(specs, "utf-8")
110-
111110
let r = "export const prelude: Record<string, string> = {\n"
112111
for (const fn of Object.keys(filecont)) {
113112
r += ` "${fn}":\n\``
@@ -122,7 +121,7 @@ function buildPrelude(folder, outp) {
122121
let curr = ""
123122
try {
124123
curr = fs.readFileSync(outp, "utf-8")
125-
} catch {}
124+
} catch { }
126125
if (curr != r) {
127126
console.log("updating " + outp)
128127
fs.writeFileSync(outp, r)
@@ -158,6 +157,8 @@ async function main() {
158157
await runTSC(["-b", "src"])
159158
await runTSC(["-b", "../cli/src"])
160159
}
160+
const ds = require("./built/devicescript-compiler.node.cjs")
161+
fs.writeFileSync("../jacs/lib/" + specname, ds.preludeFiles()[specname])
161162
} catch (e) {
162163
console.error(e)
163164
}

compiler/src/compiler.ts

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ import {
6363
TopOpWriter,
6464
Value,
6565
} from "./opwriter"
66-
import { prelude } from "./prelude"
6766
import { buildAST, formatDiagnostics, getProgramDiagnostics } from "./tsiface"
67+
import { preludeFiles } from "./specgen"
68+
import { jacdacDefaultSpecifications } from "./embedspecs"
6869

6970
export const JD_SERIAL_HEADER_SIZE = 16
7071
export const JD_SERIAL_MAX_PAYLOAD_SIZE = 236
@@ -421,11 +422,6 @@ class ClientCommand {
421422
constructor(public serviceSpec: jdspec.ServiceSpec) {}
422423
}
423424

424-
interface Position {
425-
range?: [number, number]
426-
rangeFile?: string
427-
}
428-
429425
class Program implements TopOpWriter {
430426
bufferLits = new VariableScope(null)
431427
roles = new VariableScope(this.bufferLits)
@@ -445,6 +441,7 @@ class Program implements TopOpWriter {
445441
refreshMS: number[] = [0, 500]
446442
resolverParams: number[]
447443
resolverPC: number
444+
prelude: Record<string, string>
448445
numErrors = 0
449446
main: Procedure
450447
cloudRole: Role
@@ -458,14 +455,16 @@ class Program implements TopOpWriter {
458455

459456
constructor(public host: Host, public _source: string) {
460457
this.serviceSpecs = {}
461-
for (const sp of host.getSpecs()) {
458+
const specs = host.getSpecs()
459+
for (const sp of specs) {
462460
this.serviceSpecs[sp.camelName] = sp
463461
for (const en of Object.keys(sp.enums)) {
464462
const n = upperCamel(sp.camelName) + upperCamel(en)
465463
this.enums[n] = sp.enums[en]
466464
}
467465
}
468466
this.sysSpec = this.serviceSpecs["system"]
467+
this.prelude = preludeFiles(specs)
469468
}
470469

471470
get hasErrors() {
@@ -474,7 +473,7 @@ class Program implements TopOpWriter {
474473

475474
getSource(fn: string) {
476475
if (!fn || fn == this.host.mainFileName?.()) return this._source
477-
return prelude[fn] || ""
476+
return this.prelude[fn] || ""
478477
}
479478

480479
addString(str: string | Uint8Array) {
@@ -2781,7 +2780,7 @@ class Program implements TopOpWriter {
27812780
emit() {
27822781
assert(!this.tree)
27832782

2784-
this.tree = buildAST(this.host, this._source)
2783+
this.tree = buildAST(this.host, this._source, this.prelude)
27852784
getProgramDiagnostics(this.tree).forEach(d => this.printDiag(d))
27862785

27872786
const files = this.tree.getSourceFiles()
@@ -2860,12 +2859,6 @@ class Program implements TopOpWriter {
28602859
}
28612860
}
28622861

2863-
import jacdacDefaultSpecificationsData from "../../runtime/jacdac-c/jacdac/dist/services.json"
2864-
// import * as specs from "../../runtime/jacdac-c/jacdac/dist/services.json" - slows down intellisense?
2865-
2866-
export const jacdacDefaultSpecifications =
2867-
jacdacDefaultSpecificationsData as jdspec.ServiceSpec[]
2868-
28692862
/**
28702863
* Compiles the DeviceScript program.
28712864
* @param code

0 commit comments

Comments
 (0)